曾静的技术博客

但行好事,莫问前程.

嗨,我是曾静 (@devzeng),目前暂居深圳。


这是我用来记录平日学习笔记的地方,欢迎您的访问.

iOS中使用模板引擎渲染HTML界面

在iOS实际的开发中,使用UIWebView来加载数据使用的场景特别多。很多时候我们会动态的从服务器获取一段HTML的内容,然后App这边动态的处理这段HTML内容用于展示在UIWebView上。使用到的API接口为:

- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;

由于HTML内容通常是变化的,所以我们需要动态生成HTML代码。通常我们从服务器端获取到标题、时间、作者和对应的内容,然后我们需要对这些数据处理之后拼接成一段HTML字符串。对于传统的做法是将上面的需要替换的内容填写一些占位符,放到指定的文件中如(content.html),如下所示:

<!DOCTYPE html>
<html>
    <head>
        <title>key_title</title>
    </head>
    <body>
        <div>
            <div>
                 <h2>key_title</h2>
                 <div>key_date key_author</div>
                 <hr/>
            </div>
            <div>key_content</div>
       </div>
    </body>
</html>

然后在指定的地方使用如下的方式动态生成HTML代码:

- (NSString *)loadHTMLByStringFormat:(NSDictionary *)data
{
    NSString *templatePath = [[NSBundle mainBundle] pathForResource:@"content" ofType:@"html"];
    NSMutableString *html = [[NSMutableString alloc] initWithContentsOfFile:templatePath encoding:NSUTF8StringEncoding error:nil];
    [html replaceOccurrencesOfString:@"key_title" withString:data[@"title"] options:NSCaseInsensitiveSearch range:NSMakeRange(0, html.length)];
    [html replaceOccurrencesOfString:@"key_author" withString:data[@"author"] options:NSCaseInsensitiveSearch range:NSMakeRange(0, html.length)];
    [html replaceOccurrencesOfString:@"key_date" withString:data[@"date"] options:NSCaseInsensitiveSearch range:NSMakeRange(0, html.length)];
    [html replaceOccurrencesOfString:@"key_content" withString:data[@"content"] options:NSCaseInsensitiveSearch range:NSMakeRange(0, html.length)];
    return html;
}

在实际的使用中发现还是存在不少的问题,比如我们需要对数据进行预先处理的时候需要写大量的

- (NSUInteger)replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange;

这样的替换,而且对于一些特殊的字符还需要进行特殊处理等,实在不是太友好,这样就需要一个引擎来专门处理这些事情,本文主要介绍MGTemplateEngineGRMustache的使用。

###使用模板引擎

####MGTemplateEngine的使用

MGTemplateEngineMatt Gemmell的作品,它是一个比较流行的模板引擎,它的模板语言比较类似于SmartyFreeMarkerDjango。另外它可以支持自定义的Filter(以便实现自定义的渲染逻辑),需要依赖正则表达式的工具类RegexKit

1、创建模板

<!DOCTYPE html>
<html>
    <head>
        <title></title>
    </head>
    <body>
        <div>
            <div>
                <div>
                    <h2></h2>
                    <div> </div>
                    <hr/>
                </div>
                <div><article class="post-container post-container--single" itemscope itemtype="http://schema.org/BlogPosting">
  <header class="post-header">
    <div class="post-meta">
      <time datetime="2015-01-10 20:16:21 +0800" itemprop="datePublished" class="post-meta__date date">2015-01-10</time> &#8226; <span class="post-meta__tags tags">iOS</span>
    </div>
    <h1 class="post-title">iOS中JavaScript和OC交互</h1>
  </header>

  <section class="post">
    <p>在iOS开发中很多时候我们会和UIWebView打交道,目前国内的很多应用都采用了UIWebView的混合编程技术,最常见的是微信公众号的内容页面。前段时间在做微信公众平台相关的开发,发现很多应用场景都是利用HTML5和UIWebView来实现的。</p>

<p>###机制</p>

<p>Objective-C语言调用JavaScript语言,是通过UIWebView的
<code class="highlighter-rouge">- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;</code>的方法来实现的。该方法向UIWebView传递一段需要执行的JavaScript代码最后获取执行结果。</p>

<p>JavaScript语言调用Objective-C语言,并没有现成的API,但是有些方法可以达到相应的效果。具体是利用UIWebView的特性:在UIWebView的内发起的所有网络请求,都可以通过delegate函数得到通知。</p>

<p>###示例</p>

<p>下面提供一个简单的例子介绍如何相互的调用,实现的效果是在界面上点击一个链接,然后弹出一个对话框判断是否登录成功。</p>

<p><img src="/images/uiwebview_js/uiwebview_js_demo.png" alt="uiwebview_js_demo.png" /></p>

<p>(1)示例的HTML的源码如下:</p>

<div class="highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;html&gt;</span>
    <span class="nt">&lt;head&gt;</span>
        <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"content-type"</span> <span class="na">content=</span><span class="s">"text/html;charset=utf-8"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"X-UA-Compatible"</span> <span class="na">content=</span><span class="s">"IE=Edge"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;meta</span> <span class="na">content=</span><span class="s">"always"</span> <span class="na">name=</span><span class="s">"referrer"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;title&gt;</span>测试网页<span class="nt">&lt;/title&gt;</span>
    <span class="nt">&lt;/head&gt;</span>
    <span class="nt">&lt;body&gt;</span>
        <span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"devzeng://login?name=zengjing&amp;password=123456"</span><span class="nt">&gt;</span>点击链接<span class="nt">&lt;/a&gt;</span>
    <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre>
</div>

<p>(2)UIWebView Delegate回调方法为:</p>

<div class="highlighter-rouge"><pre class="highlight"><code>- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];
    if([[url scheme] isEqualToString:@"devzeng"]) {
        //处理JavaScript和Objective-C交互
        if([[url host] isEqualToString:@"login"])
        {
            //获取URL上面的参数
            NSDictionary *params = [self getParams:[url query]];
            BOOL status = [self login:[params objectForKey:@"name"] password:[params objectForKey:@"password"]];
            if(status)
            {
                //调用JS回调
                [webView stringByEvaluatingJavaScriptFromString:@"alert('登录成功!')"];
            }
            else
            {
                [webView stringByEvaluatingJavaScriptFromString:@"alert('登录失败!')"];
            }
        }
        return NO;
    }
    return YES;
}
</code></pre>
</div>

<p>说明:</p>

<p>1、同步和异步的问题</p>

<p>(1)Objective-C调用JavaScript代码的时候是同步的</p>

<p><code class="highlighter-rouge">- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;</code></p>

<p>(2)JavaScript调用Objective-C代码的时候是异步的</p>

<p><code class="highlighter-rouge">- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;</code></p>

<p>2、常见的JS调用</p>

<p>(1)获取页面title</p>

<p><code class="highlighter-rouge">NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];</code></p>

<p>(2)获取当前的URL</p>

<p><code class="highlighter-rouge">NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];</code></p>

<p>3、使用第三方库</p>

<p><code class="highlighter-rouge">https://github.com/marcuswestin/WebViewJavascriptBridge</code></p>

<p>###使用案例</p>

<p>1、动态将网页上的图片全部缩放</p>

<p>JavaScript脚本如下:</p>

<div class="highlighter-rouge"><pre class="highlight"><code>function ResizeImages() {
	var myImg, oldWidth;
	var maxWidth = 320;
	for(i = 0; i &lt; document.images.length; i++) {
		myImg = document.images[i];
		if(myImg.width &gt; maxWidth) {
			oldWidth = myImg.width;
			myImg.width = maxWidth;
			myImg.heith = myImg.height*(maxWidth/oldWidth);
		}
	}
}
</code></pre>
</div>

<p>在iOS代码中添加如下代码:</p>

<div class="highlighter-rouge"><pre class="highlight"><code>[webView stringByEvaluatingJavaScriptFromString:  
 @"var script = document.createElement('script');"   
 "script.type = 'text/javascript';"   
 "script.text = \"function ResizeImages() { "   
     "var myimg,oldwidth;"  
     "var maxwidth=380;" //缩放系数   
     "for(i=0;i &lt;document.images.length;i++){"   
         "myimg = document.images[i];"  
         "if(myimg.width &gt; maxwidth){"   
             "oldwidth = myimg.width;"   
             "myimg.width = maxwidth;"   
             "myimg.height = myimg.height * (maxwidth/oldwidth);"   
         "}"   
     "}"
 "}\";"   
 "document.getElementsByTagName('head')[0].appendChild(script);"];
[webView stringByEvaluatingJavaScriptFromString:@"ResizeImages();"];
</code></pre>
</div>

<p>###参考资料</p>

<p>1、<a href="http://blog.devtang.com/blog/2012/03/24/talk-about-uiwebview-and-phonegap/">《关于UIWebView和PhoneGap的总结》</a></p>

<p>2、<a href="http://www.uml.org.cn/mobiledev/201108181.asp">《iOS开发之Objective-C与JavaScript的交互 》</a></p>

  </section>
</article>

<section class="read-more">
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">最近的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/blog/ios-html-template-engine.html" title="link to iOS中使用模板引擎渲染HTML界面">iOS中使用模板引擎渲染HTML界面</a></h2>
       <p class="excerpt">在iOS实际的开发中,使用UIWebView来加载数据使用的场景特别多。很多时候我们会动态的从服务器获取一段HTML的内容,然后App这边动态的处理这段HTML内容用于展示在UIWebView上。使用到的API接口为:- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;由于HTML内容通常是变化的,所以我们需要动态生成HTML代码。通常我们从服务器端获取到标题、时间、作者和对应的内容,然后我们需要对这些数据处...&hellip;</p>
       <div class="post-list__meta"><time datetime="2015-01-17 21:22:04 +0800" class="post-list__meta--date date">2015-01-17</time> &#8226; <span class="post-list__meta--tags tags">iOS</span><a class="btn-border-small" href=/blog/ios-html-template-engine.html>继续阅读</a></div>
   </div>
   
   
   
   
   <div class="read-more-item">
       <span class="read-more-item-dim">更早的文章</span>
       <h2 class="post-list__post-title post-title"><a href="/blog/ios8-touch-id.html" title="link to iOS8中使用TouchID校验用户身份">iOS8中使用TouchID校验用户身份</a></h2>
       <p class="excerpt">在iOS8中,开发者们可使用向第三方应用开放了Touch ID权限的API,以便他们在应用中使用指纹认证来完成用户认证部分。相当一部分的APP(如印象笔记、新版QQ)以及在升级后采用了Touch ID来验证用户身份,用以替代过去使用一般密码或者PIN码,如下图所示:(1)新版QQ:(2)印象笔记高级版本:本文主要介绍如何在应用中集成Touch ID来校验用户的身份。###集成步骤1、环境要求(1)开发环境:Xcode 6(iOS8 SDK)(2)设备要求:iPhone 5s、iPhone ...&hellip;</p>
       <div class="post-list__meta"><time datetime="2014-12-07 13:09:47 +0800" class="post-list__meta--date date">2014-12-07</time> &#8226; <span class="post-list__meta--tags tags">iOS</span><a class="btn-border-small" href=/blog/ios8-touch-id.html>继续阅读</a></div>
   </div>
   
</section>

<section class="post-comments">
  
  
  
  
</section>

</div>
            </div>
        </div>
    </body>
</html>

2、渲染生成HTML字符串

- (NSString *)loadHTMLByMGTemplateEngine:(NSDictionary *)data
{
    NSString *templatePath = [[NSBundle mainBundle] pathForResource:@"template" ofType:@"html"];
    MGTemplateEngine *engine = [MGTemplateEngine templateEngine];
    [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]];
    [engine setObject:data[@"title"] forKey:@"title"];
    [engine setObject:data[@"author"] forKey:@"author"];
    [engine setObject:data[@"date"] forKey:@"date"];
    [engine setObject:data[@"content"] forKey:@"content"];
    return [engine processTemplateInFileAtPath:templatePath withVariables:nil];
}

3、说明

(1)MGTemplateEngine提供的示例程序是运行在Mac OS上的,如果要使用到iOS上面需要引入Foundation框架

(2)对于运行在Xcode6以上的环境下创建的工程由于没有PCH文件可能会报错,需要在MGTemplateEngine的各个头文件中引入Foundation框架

(3)MGTemplateEngine在GitHub上的地址为https://github.com/mattgemmell/MGTemplateEngine

####GRMustache的使用

相比MGTemplateEngine来说GRMustache简单不少,

1、处理模板文件

模板文件和MGTemplateEngine的一样。

2、渲染生成HTML字符串

- (NSString *)loadHTMLByGRMustache:(NSDictionary *)data
{
    NSString *templatePath = [[NSBundle mainBundle] pathForResource:@"template" ofType:@"html"];
    NSString *template = [NSString stringWithContentsOfFile:templatePath encoding:NSUTF8StringEncoding error:nil];
    return [GRMustacheTemplate renderObject:data fromString:template error:nil];
}

3、说明

(1)renderObject使用的数据的key必须要和模板中的占位符一一对应起来

(2)GRMustache在GitHub上的地址为https://github.com/groue/GRMustache

###参考资料

1、《MGTemplateEngine - Templates with Cocoa》

2、《MGTemplateEngine 模版引擎简单使用》

3、《GRMustache Document》

最近的文章

在iOS开发中使用自定义字体

在iOS的项目开发中经常遇到需要使用一些自定义的字体文件,比如仿宋_GB2312、方正小标宋_GBK等。之前我们为了使用这些自定义的字体,在应用的资源包中放入这些字体文件。因为字体文件通常比较大,有的一个字库就达到10M以上(拿方正小标宋_GBK这个字库来说就有13M之多),这样打包后的ipa文件的体积就可能会变得很大,对于只有个别的模块需要特殊的字体样式的应用来说很不划算,那么在iOS6.0以后苹果就开放了动态加载字体的权限。下面就iOS中使用字体的这两种方式进行介绍。###使用静态字体...…

iOS继续阅读
更早的文章

iOS中JavaScript和OC交互

在iOS开发中很多时候我们会和UIWebView打交道,目前国内的很多应用都采用了UIWebView的混合编程技术,最常见的是微信公众号的内容页面。前段时间在做微信公众平台相关的开发,发现很多应用场景都是利用HTML5和UIWebView来实现的。###机制Objective-C语言调用JavaScript语言,是通过UIWebView的- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;的方法来实...…

iOS继续阅读