iOS / HTML解析 / TFHpple / NSMutab
2021-05-07 本文已影响0人
xh_0129
发现问题:
最近上架app store,被苹果各种虐,APP内新闻内容使用了web加载也被拒。。。
问题分析:
既然苹果爸爸都明确了因为使用了web的原因,那就不用好了,HTML内容解析用富文本加载吧
问题解决:
HTML内容解析,使用yykit加载~
网上教程千篇一律,只做重要的几个难点的使用说明
TFHpple的使用:
1.多层标签嵌套~
NSData *data = [content dataUsingEncoding:4];
TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data];
NSArray * elements = [doc searchWithXPathQuery:@"//p"];
for (TFHppleElement *hppleElement in elements) {
for (TFHppleElement *childElement in hppleElement.children) {
NSLog(@"%@",childElement.tagName);
NSLog(@"%@",childElement.content);
NSString *tagName = childElement.tagName;
[self parseWithTagName:tagName childElement:childElement contentString:contentString];
}
}
//contentString 为yykit需要加载的NSMutableAttributedString
- (void)parseWithTagName:(NSString *)tagName childElement:(TFHppleElement *)childElement contentString:(NSMutableAttributedString *)contentString{
if (childElement.children.count > 0) {
for (TFHppleElement *childElement111 in childElement.children) {
[self parseWithTagName:childElement111.tagName childElement:childElement111 contentString:contentString];
}
return;
}
每个标签的解析代码在此处。。。。
if ([tagName isEqualToString:@"img"]) {
}else if ([tagName isEqualToString:@"strong"]){
else if ([tagName isEqualToString:@"text"]){
}else .....
}
2.img标签解析~
网络图片加载:
if ([tagName isEqualToString:@"img"]) {
//取img的src属性拿来加载
for (NSString *key in childElement.attributes) {
if ([key isEqualToString:@"src"]) {
NSLog(@"======Img \n");
NSLog(@"childElement.src = %@",[childElement objectForKey:key]);
//占位图
YYAnimatedImageView *imageView = [[YYAnimatedImageView alloc] initWithFrame:Rect(0, 0, ScreenWidth - 24, (ScreenWidth-24)*0.6)];
NSMutableAttributedString *attachText = [NSMutableAttributedString attachmentStringWithContent:imageView contentMode:UIViewContentModeScaleToFill attachmentSize:imageView.size alignToFont:[UIFont systemFontOfSize:18] alignment:YYTextVerticalAlignmentCenter];
//占位图的起始位置 方便网络图片加载完成替换
NSInteger aaa = contentString.length;
//占位NSMutableAttributedString
[contentString appendAttributedString:attachText];
//SDWebImage加载图片
[imageView sd_setImageWithURL:[NSURL URLWithString:[childElement objectForKey:key]] placeholderImage:[UIImage imageNamed:@"place_default2"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
//根据图片实际宽高调整尺寸
int height = (ScreenWidth - 24) * image.size.height / image.size.width;
//创建新的NSMutableAttributedString
YYAnimatedImageView *imageView11 = [[YYAnimatedImageView alloc] initWithImage:image];
NSMutableAttributedString *attachText11 = [NSMutableAttributedString attachmentStringWithContent:imageView11 contentMode:UIViewContentModeScaleToFill attachmentSize:Size(ScreenWidth - 24, height) alignToFont:[UIFont systemFontOfSize:18] alignment:YYTextVerticalAlignmentCenter];
//替换占位NSMutableAttributedString
[contentString replaceCharactersInRange:NSMakeRange(aaa, attachText.length) withAttributedString:attachText11];
self.textView.attributedText = contentString;
}];
[contentString appendString:@"\n\n\n"];
}
}
}
3.text标签解析~
if ([tagName isEqualToString:@"text"]){
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n\n",content]];
[str addAttribute:NSForegroundColorAttributeName value:HexRGB(0x666666) range:NSMakeRange(0, str.length)];
[str addAttribute:NSFontAttributeName value: [UIFont systemFontOfSize:14] range:NSMakeRange(0, str.length)];
}
如果遇到类似<strong>我是strong标签</strong>,那么解析出来必定是strong标签内包含text标签,这时候遇到text标签的话,要判断父标签是否是strong,要把文本作为strong处理,而非text
if ([tagName isEqualToString:@"text"]){
if ([childElement.parent.tagName isEqualToString:@"strong"]) {
//strong处理
}else{
//常规text处理
}
}
YYKit的使用:
1.YYTextBorder:对某一段加背景色、加内边距、圆角
NSMutableAttributedString *sourceNameString = [[NSMutableAttributedString alloc] initWithString:sourceName];
[sourceNameString addAttribute:NSForegroundColorAttributeName value:HexRGB(0x999999) range:NSMakeRange(0, sourceName.length)];
[sourceNameString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:12] range:NSMakeRange(0, sourceName.length)];
YYTextBorder *border = [YYTextBorder borderWithFillColor:HexRGB(0xEDF2F7) cornerRadius:4];
border.insets = UIEdgeInsetsMake(-3, 0, -3, 0);
sourceNameString.textBackgroundBorder = border;
[contentString appendAttributedString:sourceNameString];
2.YYTextView:直接对attributedText 赋值即可
self.textView.attributedText = contentString;
3.YYAnimatedImageView:iOS14系统只能加载GIF,而静态图加载不出来
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YYAnimatedImageView(ImageShow)
@end
#import "YYAnimatedImageView+ImageShow.h"
@implementation YYAnimatedImageView(ImageShow)
+ (void)load
{
// 获取系统的方法
Method displayLayerMethod = class_getInstanceMethod(self, @selector(displayLayer:));
// 获取更新的方法
Method displayLayerNewMethod = class_getInstanceMethod(self, @selector(displayLayerNew:));
// 方法交换
method_exchangeImplementations(displayLayerMethod, displayLayerNewMethod);
}
- (void)displayLayerNew:(CALayer *)layer
{
Ivar imageIvar = class_getInstanceVariable([self class], "_curFrame");
UIImage *image = object_getIvar(self, imageIvar);
if (image)
{
layer.contents = (__bridge id)image.CGImage;
}
else
{
if (@available(iOS 14.0, *))
{
[super displayLayer:layer];
}
}
}
@end
NSMutableAttributedString的使用:
略~~~
附件 HTML内容、源码、效果:
1.HTML:
<p>我们此刻应该开始担心洛杉矶湖人和他们的超级球星组合吗?</p>
<p>在安东尼-戴维斯缺席了30场比赛回归之后的第八天,在周五的比赛日,因为脚踝扭伤缺席了20场比赛的詹姆斯终于也迎来了复出,湖人的超级二人组终于重出江湖,球队似乎熬过了最黑暗的时光。</p>
<p><img src="https://file.zhibo.tv/uploads/imgs/2021/05-05/1620211572875872_1024x683.jpg" title="1620211572875872_1024x683.jpg" alt="VCG31N1231014240.jpg"></p>
<p>然而出人意料的是,湖人输掉了接下来的两场比赛。更为不幸的是,詹姆斯在与猛龙的比赛中再度因为脚踝伤痛提前离场,并缺席了接下来周一对阵掘金的比赛(湖人意外取胜)。同时缺席比赛的还有球队首发控卫施罗德,他因为违反NBA的健康安全协议将缺席10到14天。</p>
<p>据ESPN的知名记者爆料,詹姆斯预计至少会缺席湖人接下来的两场比赛。从目前的球队状态上看,以往人们认为的湖人只要全员健康出战就可以所向披靡的态势已经荡然无存了,本赛季季后赛湖人的前景岌岌可危。上个赛季,湖人以西部老大的位置进入“泡泡”季后赛。但是此刻,詹姆斯和他的队友们真的做好准备再次踏上一条更加困难的季后赛之路了吗?</p>
<p><strong>淡定的理由:没必要在季后赛开始前过分“激动”</strong></p>
<p><img src="https://file.zhibo.tv/uploads/imgs/2021/05-05/1620211587906839_1024x683.jpg" title="1620211587906839_1024x683.jpg" alt="VCG31N1232617263.jpg"></p>
<p>对于上个赛季的季后赛,人们只会记得湖人“相对轻松”的击败了热火取得了2020赛季的总冠军,从而忽视了他们在季后赛开始前最后8场种子排位战中的低迷表现。复赛阶段,湖人首战以103-101战胜了快船,但是此后的7场比赛他们输掉了5场。</p>
<p>当然,其中很大的原因是因为湖人早已锁定了西部老大的位置,复赛阶段的战绩不再重要。主教练沃格尔更多的是在调试他们的球员阵容,包括最后引援来的维特斯,他在复赛阶段的出场总时间排在全队第四名。实验的结果是湖人在复赛阶段的进攻在所有的22支复赛队伍中排在第20位,糟糕的状态甚至延续到了季后赛首战,他们以93-100不敌开拓者,那场比赛里湖人全队三分球32投5中(命中率16%),甚至比利拉德个人(13投6中)的三分进球还少。</p>
<p><img src="https://file.zhibo.tv/uploads/imgs/2021/05-05/1620211646536799_1024x682.jpg" title="1620211646536799_1024x682.jpg" alt="VCG31N1231801093.jpg"></p>
<p>然而,季后赛第二场比赛,湖人就以23分的优势大胜对手,随后他们开启了季后赛模式,直到问鼎总冠军,也没有再给对手任何机会。</p>
<p>这就是我们常说的,常规赛和季后赛是完全不同的模式。我的前同事汤姆-哈伯斯特罗曾指出,一支球队赛季开始后的前十场比赛往往比最后十场常规赛更有参考意义。对此我一直很好奇,前十场比赛到底有什么特别之处?为什么参考标准不能是赛季任何其他时期的十场比赛呢?</p>
<p>为此我仔细研究了本赛季不同阶段的任意十场比赛之间的区别。通常战绩好的球队已经提前锁定了其季后赛的优势,他们通常会在常规赛末期选择让球队主力球员们休息。事实证明,本赛季前半段赛程的比赛确实更有参考价值,最后10场常规赛毫无参考价值,因为很多球队都为了季后赛提前雪藏他们的明星球员。(这一点类似全明星周末时期,该时期前后的比赛也会出现大规模的球员交易及主力轮休的情况,同样参考意义不大)</p>
<p>总而言之,我们没必要在湖人还没进入季后赛前就开始过分担心,卫冕冠军非常清楚该如何调整自己。</p>
<p><br></p>
<p><strong>担心的理由:艰难的季后赛之路和球星们的自身麻烦</strong></p>
<p><strong><img src="https://file.zhibo.tv/uploads/imgs/2021/05-05/1620212302359156_1024x781.jpg" title="1620212302359156_1024x781.jpg" alt="VCG31N1305023062.jpg"></strong></p>
<p>与上个赛季不同的是,上赛季的湖人早早锁定了西部老大的位置,他们可以在赛季末段选择休息。但是此刻,常规赛最后七场比赛,湖人面临着前所未有的战绩压力。</p>
<p>虽然周一那场来之不易的胜利给了湖人一丝喘息的机会。但是他们此刻只领先排名第六的独行侠0.5个胜场,只比西部第七的开拓者领先1个胜场。这意味着本周五两队的对话将可能成为决定最终排名的一场“决胜局”。</p>
<p>考虑到湖人队不太可能跌到第七名以后,他们晋级季后赛的机会还是很大的,因为他们只需要在两个主场中赢下一场就可以进入季后赛。不过他们的超巨组合戴维斯和詹姆斯的伤病成为了球队目前最不稳定的因素。</p>
<p>无论本赛季湖人最终以西部第几的身份打进季后赛,卫冕之路都会比上个赛季的季后赛更加困难。要知道上个赛季湖人最大的三个强敌快船、雄鹿和猛龙都提前爆冷出局,所以他们在分区决赛和最后的总决赛中并没有遇到真正实力匹敌的对手(这里并没有不尊重热火的意思,他们确实在季后赛里展现出了极其强硬的表现,我谈论的只是常规赛球队战绩)。</p>
<p><img src="https://file.zhibo.tv/uploads/imgs/2021/05-05/1620212309328099_1024x683.jpg" title="1620212309328099_1024x683.jpg" alt="VCG31N1231368134.jpg"></p>
<p>今年季后赛,湖人在首轮将要面对的对手会比2020年季后赛里所遇到的所有对手都强大,西部前四位的球队分别是爵士、太阳、掘金和快船,从目前的排名上看,湖人想要再次杀进总决赛的话,需要击败他们四支球队中的三支。</p>
<p>更为棘手的是,湖人的超巨组合因为各自的伤病问题都没有恢复到“100%”的状态。詹姆斯在周五复出后接受了采访,他表示:“我知道自己不会恢复到100%了,我想我的职业生涯里也再也没法恢复到自己100%的状态了。”</p>
<p>两个晚上之后,詹姆斯在与猛龙的比赛中再次因为脚踝疼痛提前离场,湖人也输掉了比赛。随后他缺席了周一对阵掘金的比赛,而且被曝出将至少缺席接下来的两场比赛。湖人非常小心的处理着他们国王的伤病,但是事实也证明,在季后赛即将到来之前,詹姆斯的脚踝问题正困扰着他。</p>
<p><img src="https://file.zhibo.tv/uploads/imgs/2021/05-05/1620212323966941_683x1024.jpg" title="1620212323966941_683x1024.jpg" alt="VCG31N1231368540.jpg"></p>
<p>詹姆斯回归后的表现低迷是意料之中的事,他得到了赛季最低分16分。与此同时,球队另一位超巨戴维斯同样表现得十分挣扎。</p>
<p>只看对阵掘金前的比赛,戴维斯在复出后的六场比赛里三分球23投4中,命中率仅为17%,他的投篮命中率是46%,真实命中率仅为45.2%,与其受伤前的58.8%相比简直是天壤之别。不过在对阵掘金的时候,戴维斯又找回了自己的超巨模式,他全场19投9中得到25分,并在最后时刻贡献关键的防守,封盖了坎帕佐的三分,帮助球队锁定了胜利。戴维斯的复苏无疑是一针强心剂。</p>
<p>除了詹眉组合,湖人还必须面临暂时没有施罗德的困境。施罗德是球队的首发控卫,他有着不俗的个人进攻能力,同时是湖人最依赖的三分投手。他的缺席不仅会让湖人接下来的赛程雪上加霜,更需要担心的是,他回归之后能否第一时间找回自己的比赛感觉。虽然球员们会在禁赛期间保持训练,但是这和正式比赛完全不同,根据施罗德的回归时间表看,他的复出时间很可能会延迟到季后赛首轮。</p>
<p>综上所述,内忧外患的湖人卫冕之路困难重重。与去年的晋级之路相比,湖人今年从首轮开始就要面临生死的角逐。如果湖人目前的困难能在两周之后得到解决(球员伤病、禁赛),可能是我们现在多虑了。反之,现在的种种不利因素都会毁了湖人本赛季卫冕的梦想。</p>
<p> </p>
<p>来源:ESPN</p>
<p>作者:Kevin Pelton</p>
<p>翻译:谭霄南</p>
<p><br></p>
2.源码:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"新闻详情";
self.textView = [[YYTextView alloc] initWithFrame:Rect(12, 0, ScreenWidth - 24, ScreenHeight - NAVIGATION_BAR_HEIGHT)];
self.textView.editable = NO;
[self.view addSubview:self.textView];
[self loadInformationDetail];
}
- (void)loadInformationDetail{
//网络数据请求
self.informationDetailResult = model1.data;
}
- (void)setInformationDetailResult:(InformationDetailResult *)informationDetailResult
{
_informationDetailResult = informationDetailResult;
NSString *title = [NSString stringWithFormat:@"%@\n\n",informationDetailResult.title];
NSString *articleTime = informationDetailResult.articleTime;
NSString *sourceName = [NSString stringWithFormat:@" %@ ",informationDetailResult.sourceName];
NSString *content = informationDetailResult.content;
NSLog(@"content = %@",content);
NSMutableAttributedString *contentString = [[NSMutableAttributedString alloc] init];
[contentString appendString:@"\n"];
//title
NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] initWithString:title];
[titleString addAttribute:NSForegroundColorAttributeName value:HexRGB(0x333333) range:NSMakeRange(0, title.length)];
[titleString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:20] range:NSMakeRange(0, title.length)];
[contentString appendAttributedString:titleString];
//articleTime
NSMutableAttributedString *articleTimeString = [[NSMutableAttributedString alloc] initWithString:articleTime];
[articleTimeString addAttribute:NSForegroundColorAttributeName value:HexRGB(0x999999) range:NSMakeRange(0, articleTime.length)];
[articleTimeString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:14] range:NSMakeRange(0, articleTime.length)];
[contentString appendAttributedString:articleTimeString];
[contentString appendString:@" "];
//sourceName
NSMutableAttributedString *sourceNameString = [[NSMutableAttributedString alloc] initWithString:sourceName];
[sourceNameString addAttribute:NSForegroundColorAttributeName value:HexRGB(0x999999) range:NSMakeRange(0, sourceName.length)];
[sourceNameString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:12] range:NSMakeRange(0, sourceName.length)];
YYTextBorder *border = [YYTextBorder borderWithFillColor:HexRGB(0xEDF2F7) cornerRadius:4];
border.insets = UIEdgeInsetsMake(-3, 0, -3, 0);
sourceNameString.textBackgroundBorder = border;
[contentString appendAttributedString:sourceNameString];
[contentString appendString:@"\n\n\n"];
NSData *data = [content dataUsingEncoding:4];
TFHpple *doc = [[TFHpple alloc] initWithHTMLData:data];
NSArray * elements = [doc searchWithXPathQuery:@"//p"];
for (TFHppleElement *hppleElement in elements) {
for (TFHppleElement *childElement in hppleElement.children) {
NSLog(@"%@",childElement.tagName);
NSLog(@"%@",childElement.content);
NSString *tagName = childElement.tagName;
[self parseWithTagName:tagName childElement:childElement contentString:contentString];
}
}
}
- (void)parseWithTagName:(NSString *)tagName childElement:(TFHppleElement *)childElement contentString:(NSMutableAttributedString *)contentString{
// NSLog(@"raw = %@",childElement.raw);
// NSLog(@"content = %@",childElement.content);
// NSLog(@"tagName = %@",childElement.tagName);
// NSLog(@"attributes = %@",childElement.attributes);
// NSLog(@"children = %@",childElement.children);
// NSLog(@"firstChild = %@",childElement.firstChild);
// NSLog(@"parent = %@",childElement.parent);
if (childElement.children.count > 0) {
for (TFHppleElement *childElement111 in childElement.children) {
[self parseWithTagName:childElement111.tagName childElement:childElement111 contentString:contentString];
}
return;
}
if ([tagName isEqualToString:@"img"]) {
for (NSString *key in childElement.attributes) {
if ([key isEqualToString:@"src"]) {
NSLog(@"======Img \n");
NSLog(@"childElement.src = %@",[childElement objectForKey:key]);
YYAnimatedImageView *imageView = [[YYAnimatedImageView alloc] initWithFrame:Rect(0, 0, ScreenWidth - 24, (ScreenWidth-24)*0.6)];
NSMutableAttributedString *attachText = [NSMutableAttributedString attachmentStringWithContent:imageView contentMode:UIViewContentModeScaleToFill attachmentSize:imageView.size alignToFont:[UIFont systemFontOfSize:18] alignment:YYTextVerticalAlignmentCenter];
NSInteger aaa = contentString.length;
[contentString appendAttributedString:attachText];
[imageView sd_setImageWithURL:[NSURL URLWithString:[childElement objectForKey:key]] placeholderImage:[UIImage imageNamed:@"place_default2"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
int height = (ScreenWidth - 24) * image.size.height / image.size.width;
YYAnimatedImageView *imageView11 = [[YYAnimatedImageView alloc] initWithImage:image];
NSMutableAttributedString *attachText11 = [NSMutableAttributedString attachmentStringWithContent:imageView11 contentMode:UIViewContentModeScaleToFill attachmentSize:Size(ScreenWidth - 24, height) alignToFont:[UIFont systemFontOfSize:18] alignment:YYTextVerticalAlignmentCenter];
[contentString replaceCharactersInRange:NSMakeRange(aaa, attachText.length) withAttributedString:attachText11];
self.textView.attributedText = contentString;
}];
[contentString appendString:@"\n\n\n"];
}
}
}else if ([tagName isEqualToString:@"strong"]){
NSLog(@"======strong \n");
[contentString appendAttributedString:[self attributedStringWithContent:childElement.content isStrong:YES]];
}else if ([tagName isEqualToString:@"text"]){
NSLog(@"======text \n");
if ([childElement.parent.tagName isEqualToString:@"strong"]) {
[contentString appendAttributedString:[self attributedStringWithContent:childElement.content isStrong:YES]];
}else if ([childElement.parent.tagName isEqualToString:@"a"]) {
[contentString appendAttributedString:[self attributedStringWithContent:childElement.content isStrong:NO]];
//如果是a标签 则去掉前后换行符\n\n
[contentString deleteCharactersInRange:NSMakeRange(contentString.length - 2, 2)];
[contentString deleteCharactersInRange:NSMakeRange(contentString.length - childElement.content.length - 2, 2)];
}else{
if ([childElement.content stringByReplacingOccurrencesOfString:@" " withString:@""].length != 0) {
[contentString appendAttributedString:[self attributedStringWithContent:childElement.content isStrong:NO]];
}
}
}else if ([tagName isEqualToString:@"br"]){
NSLog(@"======br \n");
}
self.textView.attributedText = contentString;
}
- (NSMutableAttributedString *)attributedStringWithContent:(NSString *)content isStrong:(BOOL)strong{
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n\n",content]];
[str addAttribute:NSForegroundColorAttributeName value:strong ? HexRGB(0x333333) : HexRGB(0x666666) range:NSMakeRange(0, str.length)];
[str addAttribute:NSFontAttributeName value: strong ? [UIFont boldSystemFontOfSize:16.0] : [UIFont systemFontOfSize:14] range:NSMakeRange(0, str.length)];
if (!strong) {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.firstLineHeadIndent = 28; // 首行缩进
paragraphStyle.headIndent = 0; // 其它行缩进
paragraphStyle.lineSpacing = 10; // 行间距
[str addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, str.length)];// 段落
}
return str;
}
3.效果:
IMG_0129.PNG