1. 程式人生 > >猿題庫iOS客戶端的技術細節(三):基於CoreText的排版引擎

猿題庫iOS客戶端的技術細節(三):基於CoreText的排版引擎

來自:http://blog.devtang.com/blog/2013/10/21/the-tech-detail-of-ape-client-3/

前言

本人今年主要在負責猿題庫iOS客戶端的開發,本文旨在通過分享猿題庫iOS客戶端開發過程中的技術細節,達到總結和交流的目的。

這是本技術分享系列文章的第三篇。本文涉及的技術細節是:基於CoreText的排版引擎。

CoreText概述

因為猿題庫的做題和解析介面需要複雜的排版,所以我們基於CoreText實現了自己的富文字排版引擎。我們的排版引擎對公式、圖片和連結有著良好支援,並且支援各種字型效果混排。對於內容中的圖片,支援點選檢視大圖功能,對於內容中的連結,支援點選操作。

下圖是我們應用的一個截圖,可以看到公式,圖片與文字混排良好。

對於富文字排版,除了可以用CoreText實現外,還可以用UIWebView實現。我以前寫過一篇介紹如何用UIWebView進行復雜內容顯示和互動的文章《關於UIWebView和PhoneGap的總結》,裡面介紹了使用UIWebView如何處理引數傳遞,同步與非同步等問題,感興趣的同學也可以翻看。

基於CoreText來實現和基於UIWebView來實現相比,前者有以下好處:

  1. CoreText佔用的記憶體更少,UIWebView佔用的記憶體更多。
  2. CoreText在渲染介面前就可以精確地獲得顯示內容的高度(只要有了CTFrame即可),而UIWebView只有渲染出內容後,才能獲得內容的高度(而且還需要用javascript程式碼來獲取)
  3. CoreText的CTFrame可以在後臺執行緒渲染,UIWebView的內容只能在主執行緒(UI執行緒)渲染。
  4. 基於CoreText可以做更好的原生互動效果,互動效果可以更細膩。而UIWebView的互動效果都是用javascript來實現的,在互動效果上會有一些卡頓存在。例如,在UIWebView下,一個簡單的按鈕按下效果,都無法做到原生按鈕的即時和細膩的按下效果。

當然基於CoreText的方案也有一些劣勢:

  1. CoreText渲染出來的內容不能像UIWebView那樣方便地支援內容的複製。
  2. 基於CoreText來排版,需要自己處理圖片排版相關的邏輯,也需要自己處理連結點選操作的支援。

我們最初的猿題庫行測第一版採用了基於UIWebView來實現,但是做出來發現一些小的互動細節無法做到精緻。所以後來的第二版我們就全部轉成用CoreText實現,雖然實現成本上增加了不少,但是應用的互動效果好多了。

使用CoreText也為我們後來的iPad版提供了技術積累,因為iPad版的頁面排版更加複雜,用UIWebView是完全無法完成相應的互動和排版需求的。

實現細節

服務端介面

我們在後臺實現了一個基於UBB 的富文字編譯器。使用UBB的原因是:

  1. UBB相對於HTML來說,雖然功能較簡單,但是能完全滿足我們對於富文字排版的需求。
  2. 做一個UBB的語法解析器比較簡單,便於我們將UBB渲染到各個平臺上。

為了簡化iOS端的實現,我們將UBB的語法解析在伺服器端完成。伺服器端提供了介面,可以直接獲得將UBB解析成類似HTML的檔案物件模型(DOM) 的樹型資料結構。有了這個樹型資料結構,iOS端渲染就簡單多了,無非就是遞迴遍歷樹型節點,將相關的內容轉換成 NSAttributeString即可,之後將NSAttrubiteString轉成CoreText的CTFrame即可用於介面的繪製。

支援圖文混排

支援圖文混排在教程:《Core Text Tutorial for iOS: Making a Magazine App》 中有介紹,我們在解析DOM樹遇到圖片節點時,則將該內容轉成一個空格,隨後設定該空格在繪製時,需要我們自己指定寬高相關資訊,而寬高資訊在圖片節點中都有提供。這樣,CoreText引擎在繪製時,就會把相關的圖片位置留空,之後我們將圖片非同步下來下來後,使用CoreGraph相關的API將圖片再畫在介面上,就實現了圖文混排功能。

下面的相關的示例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* Callbacks */
static void deallocCallback( void* ref ){
    [(id)ref release];
}
static CGFloat ascentCallback( void *ref ){
    CGFloat height = [(NSString*)[(NSDictionary*)ref objectForKey:@"height"] floatValue];
    return height/2 + [FrameParserConfig sharedInstance].baselineFromMid;
}
static CGFloat descentCallback( void *ref ){
    CGFloat height = [(NSString*)[(NSDictionary*)ref objectForKey:@"height"] floatValue];
    return height/2 - [FrameParserConfig sharedInstance].baselineFromMid;
}
static CGFloat widthCallback( void* ref ){
    return [(NSString*)[(NSDictionary*)ref objectForKey:@"width"] floatValue];
}
+ (void)appendDelegateData:(NSDictionary *)delegateData ToString:(NSMutableAttributedString*)contentString {
    //render empty space for drawing the image in the text //1
    CTRunDelegateCallbacks callbacks;
    callbacks.version = kCTRunDelegateCurrentVersion;
    callbacks.getAscent = ascentCallback;
    callbacks.getDescent = descentCallback;
    callbacks.getWidth = widthCallback;
    callbacks.dealloc = deallocCallback;
    CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, delegateData);
    [delegateData retain];
    // Character to use as recommended by kCTRunDelegateAttributeName documentation.
    // use " " will lead to wrong width in CTFramesetterSuggestFrameSizeWithConstraints
    unichar objectReplacementChar = 0xFFFC;
    NSString * objectReplacementString = [NSString stringWithCharacters:&objectReplacementChar length:1];
    NSDictionary * attributes = [self getAttributesWithStyleArray:nil];

            
           

相關推薦

iOS客戶技術細節基於CoreText排版引擎

來自:http://blog.devtang.com/blog/2013/10/21/the-tech-detail-of-ape-client-3/ 前言 本人今年主要在負責猿題庫iOS客戶端的開發,本文旨在通過分享猿題庫iOS客戶端開發過程中的技術細節

iOS 客戶架構設計

推薦序 我幾周前寫過一篇文章,叫 《被誤解的 MVC 和被神化的 MVVM》,其中的很多思想是和本文的作者 Lancy 交流獲得的。當時很多人回覆問:能直接上猿題庫的程式碼嗎?這次 Lancy 的這篇文章就直接上程式碼了。 這篇文章詳細介紹了猿題庫客戶端架構的設計和思考,當然,也有大量的程式碼示例。

無線 iphone客戶測試白皮書

同名 一次 消息推送 自動跳轉 資源 快速 簡單 通訊 一個數據庫 7 、 PUSH 測試 1) 檢查 push 消息是否按照指定的業務規則發送 2) 檢查不接受推送消息時,檢查用戶不會再接收到 push. 3) 如果用戶設置了免打擾的時間段,檢查在免打擾時間段內,用戶接收

dubbo-php-framework的客戶api解析

這篇我們分析生成Proxy後的處理流程,接著前面一篇文章,我們可以看到有p2p和register模式,這兩種模式最大的區別是服務地址資訊從哪裡獲取,而proxy的處理流程卻是一直的。 public static function newProxyInstance($ser

配置中心 Apollo 原始碼解析 —— 客戶 API 配置之 ConfigFile

������關注微信公眾號:【芋道原始碼】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋

ONVIF協議網路攝像機IPC客戶程式開發7裝置搜尋

1 專欄導讀 本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或程式碼段),後面文章不會贅述。為了節省篇幅,突出重點,在文章中展示的示例程式碼僅僅是關鍵程式碼,你可以在「專欄開篇」中獲取完整程式碼。

無線客戶框架設計1前言、目錄,以及一些念念碎

接下來要說的一個系列,是一個完整的App應用所需要的企業級框架設計,是我這2年來在無線客戶端這個領域摸爬滾打的,總結沉澱的心得體會,中途吃了很多虧,走過很多彎路,加了很多班,一次又一次的重構,不斷的學習,才知道,哦,原來iOS要這麼做,原來Android要那麼做,然後回過頭來再看看我最熟悉的WP,哦,原來WP

Windows客戶開發簡介

         之前的一篇文章裡,我簡單概要的介紹了一下介面庫的知識。既然是跟介面有關,那麼必然少不了很多關於繪製的內容。對於Windows開發而言,介面繪製使用的一類API就是所謂的“GDI”。          GDI這個東西可有歷史了,但是我們就不去追根朔源了。首先

ONVIF協議網路攝像機IPC客戶程式開發4使用gSOAP生成Web Services框架程式碼

1. 專欄導讀 本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或程式碼段),後面文章不會贅述。為了節省篇幅,突出重點,在文章中展示的示例程式碼僅僅是關鍵程式碼,你可以在「專欄開篇」中獲取完整程式碼。

Java提高配三七—–Java集合細節subList的缺陷

         我們經常使用subString方法來對String物件進行分割處理,同時我們也可以使用subList、subMap、subSet來對List、Map、Set進行分割處理,但是這個分割存

Linux就業技術指導IDC機房解密

輕松 ron 生產環境 身份證 分配 dell r710 試題 關機 我們 1.1 IDC機房 1.1.1 帶寬計算 帶寬流量計算公式: 1 Byte=8bit,1KB=1024B,1MB=1024KB,1GB=1024MB B表示Byte,工業標準是1000.

軟件性能測試技術----數據MySQL性能

指標 實時 代碼 瓶頸 pan 調優 存儲引擎 百度 servle 全圖: MySQL重點監控指標: MySQL主流分支: 數據庫架構設計: MySQL慢查詢: SQL語句分析與調優: MySQL索引: MySQL存儲引擎: M

Spring Cloud Ribbon(客戶負載均衡)2

1.引數配置 對於Ribbon的引數配置通常有兩種方式:全域性配置以及指定客戶端配置: 全域性配置:ribbon.<key>=<value>格式進行配置即可。<key>代表了Ribbon客戶端配置的引數名,<value>代表了對應引數值。比

unity3D-----------socket客戶、伺服器簡單

利用socket簡單的實現,客戶端和伺服器之間的通訊。 客戶端: using UnityEngine; using System.Collections; using System.Text; using System.Net; using System.Net.Soc

oracle無客戶連線資料庫C#

前提: 1. 不安裝oracle的客戶端 2. 不需要配置ora檔案 3. C# 實現   既然是C#實現,當然就要去找oracle的連線庫了,主要有三種方式: 1. 微軟提供的System.Data.OracleClient已經過時了,不推薦使用了 2. Or

dubbo-php-framework的客戶api解析

從這篇,我們開始分析客戶端的側的流程,所謂客戶端就是指dubbo-php-frame框架中consumer端,我們結合demo來看看consumer的呼叫流程,下面程式碼路徑為dubbo-php-framework-master/demo/demo-consumer/serv

dubbo-php-framework的客戶api解析

這篇我們開始解析ProxyFactory的流程,這個類重點完成了客戶端生成代理的流程,其路徑為dubbo-php-framework-master/consumer/proxy/ProxyFactory.php,我們接著dubbo-php-framework的客戶端api解析

ZooKeeper的Java客戶工具使用Curator

安裝 <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <vers

Redis的Java客戶工具使用Jedis

安裝 引入Maven依賴 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <versio

Eureka客戶初始化4

10. 上接RetryableEurekaHttpClient#execute,繼續執行clientFactory.newClient(currentEndpoint);返回的是new RedirectingEurekaHttpClient(endpoint.getServi