最近在做一个跟微博相关的应用。其中涉及到了对微博中@、##以及URL链接的解析与展示。分享一下个人处理的方式,希望对需要的人有所帮助。

最终的展现效果:

首先,第一步是你得从纯文本中找到它们。毫无疑问,采用正则表达式匹配是最佳的方式。我采用的是RegexKitLite库。

解析这三种格式的正则表达式如下:

  1. /*****************************regular expressions**************************/
  2. #define ALABEL_EXPRESSION @"(<[aA].*?>.+?</[aA]>)"
  3. #define HREF_PROPERTY_IN_ALABEL_EXPRESSION @"(href\\s*=\\s*(?:\"([^\"]*)\"|\'([^\']*)\'|([^\"\'>\\s]+)))"
  4. #define URL_EXPRESSION @"([hH][tT][tT][pP][sS]?:\\/\\/[^ ,'\">\\]\\)]*[^\\. ,'\">\\]\\)])"
  5. #define AT_IN_WEIBO_EXPRESSION @"(@[\u4e00-\u9fa5a-zA-Z0-9_-]{4,30})"
  6. #define TOPIC_IN_WEIBO_EXPRESSION @"(#[^#]+#)"

分别为:匹配<a></a>标签,匹配a标签的href属性,匹配URL地址,匹配微博中的@,匹配微博中的##(topic);

对于文本的处理:

  1. - (NSString*)handleForShowing{
  2. NSArray *expressions = expressions = [[NSArray alloc] initWithObjects:
  3. AT_IN_WEIBO_EXPRESSION,
  4. TOPIC_IN_WEIBO_EXPRESSION,
  5. URL_EXPRESSION,
  6. nil];
  7. //如果有<a></a>则先进行预处理
  8. NSString *aLabelExpression=@"(<[aA].*?>.+?</[aA]>)";
  9. if ([self stringByMatching:aLabelExpression]) {
  10. NSArray *matchedArr=[self componentsMatchedByRegex:ALABEL_EXPRESSION];
  11. for (NSString *matchedItem in matchedArr) {
  12. NSString *tmpHrefVal=[[matchedItem stringByMatching:HREF_PROPERTY_IN_ALABEL_EXPRESSION]
  13. stringByMatching:URL_EXPRESSION];
  14. if (tmpHrefVal) {
  15. self=[self replaceAll:matchedItem with:tmpHrefVal];
  16. }
  17. }
  18. }
  19. for (NSString *expression in expressions)
  20. {
  21. NSString *replaceStr=@"";
  22. if ([expression contains:@"@"]) {
  23. replaceStr=@"<a href=\"$1\">$1</a>";
  24. }else if([expression contains:@"#"]){
  25. replaceStr=@"<a href=\"$1\">$1</a>";
  26. }else{
  27. replaceStr=@"<a href=\"$1\">$1</a>";
  28. }
  29. self=[self stringByReplacingOccurrencesOfRegex:expression withString:replaceStr];
  30. }
  31. [expressions release];
  32. return self;
  33. }

这里需要注意的是,微博的种类有很多种。大部分的地址都直接是纯粹的Url,但其中的一种微博(记不清是视频还是音乐的)返回的url是附带在a标签的内部作为href属性的。这样就不便于统一处理,所以我采取的做法是:首先,让解析流程统一化。也就是先把文本中包含的a标签去掉,把href包含的链接作为纯文本。然后,解析出微博中的这三种特殊字符串,并为其包裹一层a标签。

接着,谈谈关于展示的问题。上面你可能想知道为什么需要包上一层a标签呢?那是为了展示用的。

如何让@、##、URL高亮呢,我目前只找到三种展示它的方式:

(1)   Three 20中的TTStyledTextLabel

(2)   原先用于展示Twitter,后来被改写支持中文展示的FancyLabel

(3)   最擅长呈现html标记的UIWebView

三种我全部试过,最后还是选择了UIWebView。下面说明一下未曾使用前两种的原因。

其实,原本我是不倾向于使用UIWebView,我想能使用普通的控件,就无需把UIWebView这种大部头搬出来“救场”了(据说UIWebView的内存泄露问题由来已久,后续我会谈到这个问题,本篇不作深究)。

我首先尝试的是第二种:FancyLabel。开始使用的时候,觉得好像真能展示。它文件内部已经存在了解析的正则表达式了,并且RegexKitLite也是作为它的组件使用的(可见原理都是一样的)。但展示了几个发现:@、##、URL各种不同方式的复杂搭配,它显得有些无能为力(这其实是它附带的正则表达式匹配得不够健全的问题),但当时我却不是这个原因放弃它的。放弃的原因是,它无法“折断换行”,也就是,当一个匹配项它呈现的位置已经在一行的末尾了,它无法呈现匹配项的一部分,同时将另一部分折断到下一行的起始去显示,它的处理方式时另起一行。这看起来非常难看,后面还有空间空出来了,就直接跑到下面一行去展示了,并且该行它也是独占的,后面的文本也不得不另起一行,显得非常不流畅。最致命的是:你无法算准它的高度,因为它归根到底是一个UILabel的子类。对于Label的高度,在它的宽度固定的情况下,通常都是带着它文本的字体大小算出来的。但这个时候,你已经无法准确地计算高度了(因为普通的计算方式,它默认Label文本的呈现方式是那种“流式”的,你换行起始占用了增大了它的高度,但在算的时候你无法将这些情况估算到),所以它影响了接下来用于呈现评论/转发等控件的布局。

放弃了第二种,又在网上寻找其他的解决方案,发现大名鼎鼎的Three 20里面,有一种呈现富客户端文本的控件:TTStyledTextLabel,支持对连接、简单html标签以及样式。那我将这些解析出来的内容,包裹上<a></a>不就可以了吗?我当时就是这么想。结果同样不是太理想,也是无法折断换行的原因。当然,如果你下面没有依赖它来布局的控件(如同你在web中使用的是绝对定位,而不是相对定位一样)。那么你还是可以使用它的。

这可不像FancyLabel,你直接把接受到的纯文本丢给它一了百了。它自身只负责普通html标签以及链接的解析,所以你给它的文本必须是处理好之后的。其实,你处理好之后展现也是没有问题的。如果你使用的是UITableView的方式来展示它,并且你自定义了UITableViewCell来呈现它,会显得很麻烦。因为这个部分可能要计算两次高度:在heightForRowAtIndexPath代理方法中算一次,在自定义的Cell内部,为了下面控件的布局,必须算一次。其实,TTStyledTextLabel自身是可以返回高度的,并且它返回的高度是正确的(即使它有些匹配项是另起一行的,但占用的“额外”高度也被它包含在内,这也是我认为它很强大的地方)。但,在heightForRowAtIndexPath计算起来就不那么简单了,我简单得把一样的文本给一个“帮助方法”,它内部构建一个TTStyledTextLabel对象,获取到文本,并算高度,还是有所偏差。所以说能不能使用,主要是看你用怎样的方式来展示你的微博内容。如果你想用,这样是不够的,因为它只是完成了呈现的工作。使用过新浪微博或者腾讯微博客户端的人都知道,@、##、URL这些高亮文本是可以点击的。很遗憾的是,TTStyledTextLabel自身对于a标签的点击事件仅仅只是,用它内部的另一个浏览器组件来加载href属性的URL,这显然不是我们想要的。为了改变它这种默认行为,我继承了TTStyledTextLabel,重写了它的点击事件,以拦截它的默认行为:

  1. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
  2. TTTableView* tableView = (TTTableView*)[self isKindOfClass:[TTTableView class]];
  3. if (!tableView) {
  4. if (self.highlightedNode) {
  5. // nodes to converse with.
  6. if ([self.highlightedNode isKindOfClass:[TTStyledLinkNode class]]) {
  7. //NSLog([(TTStyledLinkNode*)_highlightedNode URL]);
  8. } else if ([self.highlightedNode isKindOfClass:[TTStyledButtonNode class]]) {
  9. //NSLog([(TTStyledButtonNode*)_highlightedNode URL]);
  10. } else {
  11. NSLog(@"others");
  12. }
  13. self.highlightedNode=nil;
  14. }
  15. }
  16. }

在上段代码中:NSLog(@”others”);部分,你可以去实现你的逻辑:比如点击@XXX,弹出XXX的个人详情。你可以在地址中包含你需要的数据,在上面可以通过获得url来得到你的数据。

你可能会好奇,为何这两种方式都出现这种无法折断换行的行为呢。这也是由它们的实现方式决定的。你看到上面这段代码中,比如:TTStyledLinkNode、TTStyledButtonNode,它把相应的匹配项都转化为特定的Node,对这段Node单独绘制(这里牵扯到CoreText以及NSAttributeString等,具体未有空详细研究),比如某个子节点是可点击的,那可能就是TTStyledButtonNode类型,也就形如一个Button。很明显,一个Button内的文本,如果在一个区域显示不下,只能另起一行了。

要应对这种方式,看来不得不请出:UIWebView。它本身也擅长于图文混排以及富文本的呈现。你只要按照上面的方式处理好文本,然后在UIWebView里设置相关样式,就可以完美呈现,甚至图片都省去了获取并处理的过程。形如:

一不用二不休,下面的转发与评论的列表,也顺便用它来展示吧。

展示的问题完美地解决了,下面还要能够响应点击事件。这里同样要改变UIWebView中a标签的默认行为,使其响应本地调用(obj-c代码)。怎么办呢?用js给a标签注册一个click event,然后它调用一个方法,发起一个请求:

  1. sendCommand: function (cmd,param){
  2. var url="FEB:"+cmd+":"+param;
  3. document.location = url;
  4. }

它其实并不是一个真实意义上的url地址,只是一个携带了操作命令以及参数的“virtual url”。发起的任何请求都会被:

UIWebView 的shouldStartLoadWithRequest代理方法截获。

然后在这里,你可以判断相关的请求行为,获取参数,进行你的本地处理,比如弹出XXX的详情的模式窗口:

  1. - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request
  2. navigationType:(UIWebViewNavigationType)navigationType {
  3. NSString *requestString = [[request URL] absoluteString];
  4. NSArray *components = [requestString componentsSeparatedByString:@":"];
  5. if ([components count] > 1 &&
  6. [(NSString *)[components objectAtIndex:0] isEqualToString:[@"FEB" lowercaseString]]) {
  7. NSString *cmdName=(NSString *)[components objectAtIndex:1];
  8. if([cmdName isEqualToString:@"loadRepublishList"]) {
  9. } else if([cmdName isEqualToString:@"loadCommentList"]){
  10. }
  11. }
  12. }

这篇就分享到这里,下面准备就UIWebView的使用分享一些经验。比如使用一些模板引擎来增强代码的可读性以及提升开发效率。对于“微博详情界面”我使用了两个模板:一个是用于呈现微博本身的HTML模板引擎;另一个是JS模板引擎(为了提升响应速度,并且为了配合转发/评论列表的异步加载)。

iOS解析新浪微博的@##以及URL链接并展示的更多相关文章

  1. php url链接地址传数组方法 json_decode解析数组失败 经过url链接的json数组解析出错的解决方法 (原)

    先说出现的问题: 请求一个接口(例如  http://www.a.com/getmes.php)需要传一个数组参数 param ,值为 数组 array(0=>'刘师傅',1=>'1760 ...

  2. ios系统中各种设置项的url链接

    ios系统中各种设置项的url链接 在代码中调用如下代码:NSURL*url=[NSURL URLWithString:@"prefs:root=WIFI"];[[UIApplic ...

  3. URL链接后面的参数解析,与decode编码解码;页面刷新回到顶部jquery

    function request() { var urlStr = location.search; ) { theRequest = []; return; } urlStr = urlStr.su ...

  4. JavaScript实现http地址自动检测并添加URL链接

    一.天生我材必有用 给http字符自动添加URL链接是比较常见的一项功能.举两个我最近常用到的自动检测http://地址并添加链接的例子吧,首先是QQ邮箱,在使用QQ邮箱时,如果输入了URL地址(ht ...

  5. iOS 9的 Universal Links 通用链接使用

    前段时间和朋友(@品味生活)一起搞 iOS9的通用链接,我主要做了前面官方文档翻译工作,后面的一些东西都是他在搞,整理也是他整理的. 他的博客原文地址:http://pinwei.blog.51cto ...

  6. 网页解析的全过程(输入url到展示页面)

    1.用户输入网址,浏览器发起DNS查询请求 用户访问网页,DNS服务器(域名解析系统)会根据用户提供的域名查找对应的IP地址. 域名解析服务器是基于UDP协议实现的一个应用程序,通常通过监听53端口来 ...

  7. Android 通过URL scheme 实现点击浏览器中的URL链接,启动特定的App,并调转页面传递参数

    点击浏览器中的URL链接,启动特定的App. 首先做成HTML的页面,页面内容格式如下: <a href="[scheme]://[host]/[path]?[query]" ...

  8. Python 网络爬虫 009 (编程) 通过正则表达式来获取一个网页中的所有的URL链接,并下载这些URL链接的源代码

    通过 正则表达式 来获取一个网页中的所有的 URL链接,并下载这些 URL链接 的源代码 使用的系统:Windows 10 64位 Python 语言版本:Python 2.7.10 V 使用的编程 ...

  9. [CareerCup] 10.6 Find Duplicate URLs 找重复的URL链接

    10.6 You have 10 billion URLs. How do you detect the duplicate documents? In this case, assume that ...

随机推荐

  1. PM2使用文档

    简介 PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控.自动重启.负载均衡等,而且使用非常简单. 下面就对PM2进行入门性的介绍,基本涵盖了PM2的常用的功能和 ...

  2. 文件上传Django

    当Django在处理文件上传的时候,文件数据被保存在request.FILES FILES中的每个键为<input type="file" name="" ...

  3. JAVA之反射(一)

    反射(一) ** 注:博主的这篇文章是在学习反射的时间写的如有问题请及时联系博主进行修改 ** 何为反射  这里也不说一些很官方的语言了,官方的说明看着头痛,总之一句话,就是在JAVA的运行状态的时候 ...

  4. postman将上一个请求的结果作为下一个请求的数据

    需要在Tests中写入如下代码: var jsonData = JSON.parse(responseBody); postman.setGlobalVariable("token" ...

  5. Linux与DOS的常用命令比较

    命令类型 DOS Linux DOS示例 Linux示例 复制文件   copy cp copy c:\teacher1\file1 d:\tmp cp /home/teacher1/file1 /t ...

  6. ThinkPHP- 3.1

    基础: 1. 基础概念 LAMP LAMP是基于Linux,Apache,MySQL和PHP的开放资源网络开发平台.这个术语来自欧洲,在那里这些程序常用来作为一种标准开发环境.名字来源于每个程序的第一 ...

  7. 为什么有人会觉得IT门槛低,工资高?

    今天在高铁上,翻着逼乎,被一个话题勾住了,"为什么很多人会觉得IT门槛低?" 我一惊,还真是,身边朋友都觉得"IT赚的多","程序员工资高" ...

  8. Spring Cloud Ribbon负载均衡

    目录 一.简介 二.客户端负载均衡 三.RestTemplate详解 GET请求 POST请求 PUT请求 DELETE请求 一.简介 ​ Spring Cloud Ribbon是一个基于HTTP 和 ...

  9. 手机uc浏览器,获取到图片,但左上有小图标的问题

    手机uc浏览器有个坑 获取不到图片,左上是这样的

  10. 分享一个WPF下日历控件(Calendar)的样式

    WPF日历控件的一个样式 WPF自带的日历控件样式可能会比较丑,要修改其样式看起来挺复杂的,实际上很简单,用Blend打开,修改三个模板,基本就能改变全部面貌,也很容易 先上图 样式如下: <S ...