1. 程式人生 > >OS開發小記:iOS富文本框架DTCoreText在UITableView上的使用

OS開發小記:iOS富文本框架DTCoreText在UITableView上的使用

比例 編輯 dequeue all msd should 網易新聞客戶端 返回 富文本

要在頁面中顯示自己的布局,比如文字的字體和顏色、圖文並排的樣式,我們要用iOS SDK的原生UI在app本地搭建,如果一個頁面需要在服務器端獲取數據的話,我們也要在本地搭建好固定的布局,解析服務器傳回的Json數據去進行填充。但是如果一個頁面的布局是不固定的話,比如一個web頁面,用HTML與CSS封裝,我們通常會使用UIWebView進行處理。但是這樣處理等於是是把某一個頁面純粹當成了Web頁面進行渲染,在移動app開發中,這樣的情況被稱為“非原生”,不僅會失去渲染速度和交互體驗,當一個頁面上既有服務器傳回的HTML與CSS樣式又有原生的UI控件時,UIWebView就會變得特別不好處理,與其他UI控件間的通信也顯得很不協調。

UIWebView處理不好這種問題的原因,是因為它遵循瀏覽器解析web頁面的方式:即逐句解析 。這種解析方式類似於解釋型語言,或稱腳本語言,只需要下載整段代碼中的一部分代碼就可以渲染出效果。這不同於一般意義上的編譯型語言,如C、C++、Objective-C等,它是在運行的階段去進行翻譯,因此會喪失很多的效率。此外,蘋果還為它的webview提供了很多強大的功能,除了最基本的渲染網頁上的文字、圖片、視頻以及交互外,它甚至還可以直接渲染doc、pdf、excel、ppt等一系列常用的文件格式。這麽強大的一個控件對系統資源的占用可想而知

解決這個問題的辦法是:把Html+Css字符串直接由服務器傳到本地,調用iOS/OS X底層的渲染引擎CoreText去進行渲染。換句話說,這種渲染方式是輕量級的,它去掉了許多Web渲染的繁雜步驟和功能,直接根據Html+Css去渲染圖片、文字的樣式。當然,根據你的需求,你也可以決定是否處理超鏈接和顯示視頻等。

如下圖所示,網易新聞客戶端等很多流行的app就是采用類似的方法:

技術分享圖片

技術分享圖片

在這個頁面中,布局肯定是不確定,因為所有的文章都是由網易的網編用某種富文本編輯器編寫的,雖然會對移動端做一些優化。而下方又有原生的UITableViewCell(評論、其他新聞)以及UIButton等控件,在這種情況下用一個WebView嵌入肯定是不恰當的。

非常感謝來自澳洲的Oliver Drobnik,秉著開源和分享的精神,為我們提供了完美的框架——DTCoreText。DTCoreText就是筆者所要使用的這種輕量級的Html+Css樣式的解析框架。

以下是在GitHub上的地址以及文檔:

GitHub:https://github.com/Cocoanetics/DTCoreText

接口文檔說明:https://docs.cocoanetics.com/DTCoreText/

筆者推薦用CocoaPods對DTCoreText進行導入。因為在GitHub上下載的demo中會缺一個子工程:DTFoundation。你還得把這個子工程導入到原工程中。另外如果你想研究以下DTCoreText的底層實現,可以看看下面這篇文章:http://blog.cnbang.net/tech/2630/ 。下面筆者就最近開發遇到的問題做一些總結。

技術分享圖片

技術分享圖片

如上圖所示,左圖的布局中最上部是一個UIImageView,下面是一個分Section的UITableView,包括右圖所示的顯示評論條目的tableview。下部的評論框是一個單獨的UIView控件。在第二個cell中能看到帶樣式的文字和圖片,這個cell就是使用了DTCoreText的結果。在這裏,主要是使用

以下是部分核心代碼:

  • @interface DiscoverDetailViewController ()<DiscoverDetailTableViewCellDelegate,DTAttributedTextContentViewDelegate>
  • BOOL _useStaticRowHeight;
  • // NSArray *_snippets;
  • NSString * const AttributedTextCellReuseIdentifier = @"AttributedTextCellReuseIdentifier";

cellCache類似於tableview的緩存池,用於存放已經生成過的cell

  • - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  • else if (indexPath.section == 2)
  • DTAttributedTextCell *dtcell = (DTAttributedTextCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];

在上面這個確定cell的方法中,省略號部分是其他沒有用到DTAttributedTextCell的處理邏輯,可以不破壞這些邏輯,而在你指定的某個indexpath中用到DTCoreText的處理邏輯:類似於tableview的緩存池一樣的處理方法。

  • - (DTAttributedTextCell *)tableView:(UITableView *)tableView preparedCellForIndexPath:(NSIndexPath *)indexPath
  • // workaround for iOS 5 bug
  • NSString *key = [NSString stringWithFormat:@"%ld-%ld", (long)indexPath.section, (long)indexPath.row];
  • DTAttributedTextCell *cell = [cellCache objectForKey:key];
  • if ([self _canReuseCells])
  • cell = (DTAttributedTextCell *)[tableView dequeueReusableCellWithIdentifier:AttributedTextCellReuseIdentifier];
  • cell = [[DTAttributedTextCell alloc] initWithReuseIdentifier:AttributedTextCellReuseIdentifier];
  • cell.accessoryType = UITableViewCellAccessoryNone;
  • cell.hasFixedRowHeight = _useStaticRowHeight;
  • // cache it, if there is a cache
  • [cellCache setObject:cell forKey:key];
  • [self configureCell:cell forIndexPath:indexPath];
  • - (void)configureCell:(DTAttributedTextCell *)cell forIndexPath:(NSIndexPath *)indexPath
  • // NSDictionary *snippet = [_snippets objectAtIndex:indexPath.row];
  • NSString *html = [_dataDictionary objectForKey:@"content"];
  • [cell setHTMLString:html];
  • [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
  • cell.attributedTextContextView.shouldDrawImages = YES;
  • cell.attributedTextContextView.delegate = self;
  • cell.attributedTextContextView.backgroundColor = [ANGUIColorPlus colorWithHexString:@"#efeff4"];
  • - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
  • else if(section == 2)
  • DTAttributedTextCell *cell = (DTAttributedTextCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];
  • return [cell requiredRowHeightInTableView:tableView];

看到上面這三個方法,筆者感覺非常熟悉,DTCoreText非常遵循UITableView原本的設計方法,對cell的重用管理做得很好。而且,關於確定高度的方法DTAttributedTextCell也給出了一個封裝好的自適應高度的方法,使用起來非常方便。

  • // reuse does not work for variable height
  • if ([self respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)])
  • // only reuse cells with fixed height

上面這個方法用於判斷適應當前高度的cell是否可重用,如果可以重用就不需要重新加載了。

此外,如果要加載圖片,還需要實現下面這個代理方法:

  • #pragma mark - DTAttributedTextContentViewDelegate
  • - (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame{
  • if([attachment isKindOfClass:[DTImageTextAttachment class]]){
  • CGFloat aspectRatio = frame.size.height / frame.size.width;
  • CGFloat width = dUISize_Screen_Width - 16*2;
  • CGFloat height = width * aspectRatio;
  • UIView *View = [[UIView alloc] initWithFrame:frame];
  • UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,width,height)];
  • imageView.backgroundColor = [UIColor grayColor];
  • [imageView sd_setImageWithURL:attachment.contentURL placeholderImage:dImage_loadPicDefault_LongSquare options:EMSDWebImageProgressiveDownload];
  • imageView.canClick = YES;
  • imageView.contentMode = UIViewContentModeScaleAspectFit;
  • [View addSubview:imageView];

這個代理方法會根據當前要顯示圖片的url和frame返回一個用於顯示圖片的UIView。筆者在這裏使用SDWebImage作為顯示網絡圖片的方式。然後添加一個UIImageView在View上,這樣做可以根據屏幕的大小按比例縮放要顯示的圖片,以適應顯示。

以上就是實現在UITableView上使用DTCoreText的主要過程。加載的過程很快,滑動時也不會實現卡頓,很流暢。

https://blog.csdn.net/lala2231/article/details/50780842

OS開發小記:iOS富文本框架DTCoreText在UITableView上的使用