<原>DTCoreText學習(三)-自定義DTAttributedTextCell


原創博文,未經作者允許,不允許轉載

DTCoreText自帶的DTAttributedTextCell在顯示html的時候  會占用整個cell的大小,當我們需要的形式比較靈活的時候,或者想在cell上自定義添加更多的東西的時候  DTAttributedTextCell 就會變的不夠用 需要我門根據DTAttributedTextCell的原理,自己寫一個cell  

例如 我們希望cell左邊是一個圖片,然后右邊剩下的區域是一個DTAttributedTextContentView用來顯示html 這個圖片在點擊cell的時候會改變

步驟


1.首先 仍然是將DTCoreText添加到我們自己的工程文件中

2.創建UITableViewCell的子類 MyCell.h MyCell.m

MyCell.h

 1 @interface MyCell : UITableViewCell
 2 {
 3    
 4      IBOutlet UIImageView *imageView;
 5      IBOutlet DTAttributedTextContentView *_attributedTextContextView;
 6 }
 7 
 8 @property(nonatomic,retain)UIImageView *imageView;
 9 
10 @property (nonatomic, strong) NSAttributedString *attributedString;
11 @property (nonatomic, readonly) DTAttributedTextContentView *attributedTextContextView;
12 - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier accessoryType:(UITableViewCellAccessoryType)accessoryType;
13 
14 - (void)setHTMLString:(NSString *)html;
15 
16 - (CGFloat)requiredRowHeightInTableView:(UITableView *)tableView;
17 
18 @end

storyboard中 左邊是圖片  右邊是UIView  將其class設為DTAttributedTextContentView  

將cell的class設置為MyCell

並且 都與MyCell進行連接

這樣當我們調用mycell的 setHTMLString:(NSString*)html方法時候  實際上是在設置右邊的attributedTextContextView.attributedString  為我們解析過后的string  然后顯示出來

MyCell.m


直接將DTAttributedTextCell.m中的所有代碼代碼復制過來即可 但是要更改幾處地方

 1 #import "MyCell.h"
 2 #import "DTCoreText.h"
 3 #import "DTAttributedTextCell.h"
 4 @implementation MyCell
 5 {
 6     
 7     NSAttributedString *_attributedString;
 8     //DTAttributedTextContentView *_attributedTextContextView;   //改動1
 9     
10     NSUInteger _htmlHash; // preserved hash to avoid relayouting for same HTML
11 }
12 @synthesize attributedString = _attributedString;
13 @synthesize attributedTextContextView = _attributedTextContextView;
14 
15 @synthesize imageView;   //添加這一句  改動2

在.m文件中 {}中的屬性是私有屬性  由於我們在MyCell.h中聲明了 DTAttributedTextContentView *_attributedTextContextView;   所以這里不用再次聲明

 

 1 - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier accessoryType:(UITableViewCellAccessoryType)accessoryType
 2 {
 3     self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
 4     if (self) 
 5     {
 6         // don't know size jetzt because there's no string in it
 7         
 8         //_attributedTextContextView = [[DTAttributedTextContentView alloc] init];
 9         //_attributedTextContextView.frame=CGRectMake(100, 100, 40, 50);
10         
11         _attributedTextContextView.edgeInsets = UIEdgeInsetsMake(5, 5, 5, 5);
12         
13     //    [self.contentView addSubview:_attributedTextContextView];
14        
15         
16         
17         
18     }
19     return self;
20 }

這里面注釋掉的代碼是 原來的cell  由於DTAttributedTextCell 沒有用storyboard或者 xib 所以 它上面的

DTAttributedTextContentView 是用代碼  在初始化的時候 addSubView上去的  我們的MyCell 是用storyboard顯式創建的

所以這里面不用這些初始化代碼  

這時需要更改的幾個地方 其他的直接復制過來即可

 

3.在tableView中引用MyCell

這里只貼出關鍵部分代碼

 1 - (void)configureCell:(MyCell *)cell forIndexPath:(NSIndexPath *)indexPath
 2 {
 3     
 4     NSString *html=[array objectAtIndex:indexPath.row];
 5     [cell setHTMLString:html];
 6     cell.imageView.image=[UIImage imageNamed:@"XX"];
 7     
 8     cell.attributedTextContextView.shouldDrawImages = YES;
 9 }
10 
11 - (MyCell *)tableView:(UITableView *)tableView preparedCellForIndexPath:(NSIndexPath *)indexPath
12 {
13         //DTCoreTest Demo 中的源代碼  這里將 其注釋掉 
14     //static NSString *cellIdentifier = @"name";
15     
16     if (!cellCache)
17     {
18         cellCache = [[NSCache alloc] init];
19     }
20     
21     // workaround for iOS 5 bug
22     NSString *key = [NSString stringWithFormat:@"%d-%d", indexPath.section, indexPath.row];
23     
24     MyCell *cell = [cellCache objectForKey:key];
25     
26     if (!cell)
27     { 
28         // reuse does not work for variable height
29         //cell = (DTAttributedTextCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
30         
31         if (!cell)
32         {
33                         //DTCoreTest Demo 中的源代碼  這里將 其注釋掉 
34             //cell = [[MyCell alloc] initWithReuseIdentifier:cellIdentifier accessoryType:UITableViewCellAccessoryDisclosureIndicator];
35             //這一句是引用我們自定義的cell的重點代碼
36             cell=[tableView dequeueReusableCellWithIdentifier:@"name"];
37            
38             
39         }
40         
41         // cache it
42         [cellCache setObject:cell forKey:key];
43     }
44     
45     [self configureCell:cell forIndexPath:indexPath];
46     
47     return cell;
48 }
49 
50 // disable this method to get static height = better performance
51 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
52 {
53     MyCell *cell = (MyCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];
54     //下面一句是非常重要的一句代碼
55     return  cell.attributedTextContextView.frame.size.height+10;
56    //DTCoreTest Demo 中的源代碼  這里將 其注釋掉 改為上面一句
57     //return [cell requiredRowHeightInTableView:tableView];
58 }
59 
60 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
61 {
62     MyCell *cell = (MyCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];
63     
64     return cell;
65 }

填充數據方面的代碼比較簡單  這里就不貼出來了

這時后點擊運行  顯示如下圖所示

很明顯出現了錯誤,  DTAttributedTextContentView 仍然占據整個cell   而imageView  就是那個A  顯示的位置正確 但卻因為

DTAttributedTextContentView 占據整個cell  而導致其覆蓋在上面  並沒有按照我們設計布局的  左邊顯示 A  右邊顯示html

但是有一點是確定的     自適應高度沒有問題


我們先來分析一下tableView中 那些關鍵代碼的  運行流程

首先調用

1 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
2 {
3     MyCell *cell = (MyCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];
4      
5     return  cell.attributedTextContextView.frame.size.height+10;
6    
7     //return [cell requiredRowHeightInTableView:tableView];
8 }

來確定每一個cell的高度  

在這里面繼續調用

 1 - (MyCell *)tableView:(UITableView *)tableView preparedCellForIndexPath:(NSIndexPath *)indexPath
 2 {
 3     //static NSString *cellIdentifier = @"name";
 4     
 5     if (!cellCache)
 6     {
 7         cellCache = [[NSCache alloc] init];
 8     }
 9     
10     // workaround for iOS 5 bug
11     NSString *key = [NSString stringWithFormat:@"%d-%d", indexPath.section, indexPath.row];
12     
13     MyCell *cell = [cellCache objectForKey:key];
14     
15     if (!cell)
16     {
17         // reuse does not work for variable height
18         //cell = (DTAttributedTextCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
19         
20         if (!cell)
21         {
22             //cell = [[MyCell alloc] initWithReuseIdentifier:cellIdentifier accessoryType:UITableViewCellAccessoryDisclosureIndicator];
23             cell=[tableView dequeueReusableCellWithIdentifier:@"name"];
24            
25             
26         }
27         
28         // cache it
29         [cellCache setObject:cell forKey:key];
30     }
31     
32     [self configureCell:cell forIndexPath:indexPath];
33     
34     return cell;
35 }

這個方法中 有兩句核心代碼  其他的都是一些緩存相關 看一下代碼很好理解

cell=[tableView dequeueReusableCellWithIdentifier:@"name"];

[self configureCell:cell forIndexPath:indexPath];

第一句  是在緩存中沒有可取的 cell的時候 從tableView的可重用隊列中取出一個cell實例(可參考《自定義UITableViewCell的理解一文》)

第二句 是配置cell  接下來看看  這個方法的代碼

1 - (void)configureCell:(MyCell *)cell forIndexPath:(NSIndexPath *)indexPath
2 {
3     
4     NSString *html=[array objectAtIndex:indexPath.row];
5     [cell setHTMLString:html];
6     cell.imageView.image=[UIImage imageNamed:@"xxx"];7     
8     cell.attributedTextContextView.shouldDrawImages = YES;
9 }

這里面是初始化cell上的圖片 以及DTAttributedTextContentView

核心代碼是[cell setHTMLString:html];  前面有介紹 

通過單步跟蹤調試  跟蹤以后的調用流程

這里先進入setHTMLString

 1 - (void)setHTMLString:(NSString *)html
 2 {
 3     // we don't preserve the html but compare it's hash
 4     NSUInteger newHash = [html hash];
 5     
 6     if (newHash == _htmlHash)
 7     {
 8         return;
 9     }
10     
11     _htmlHash = newHash;
12     
13     NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
14     NSAttributedString *string = [[NSAttributedString alloc] initWithHTML:data documentAttributes:NULL];
15     self.attributedString = string;
16 }

這一段的核心代碼是最后一句   前面大部分都是對html的處理 包括解析等等 如果單步跟蹤進入

會發現  

NSAttributedString *string = [[NSAttributedString alloc] initWithHTML:data documentAttributes:NULL];調用的很

深 很深 里面包括對html的各種處理  解析 等等  

在跟蹤的時候發現了設置 顯示的html的字體大小的方法  

在DTHTMLAttributedStringBuilder.m中 的 buildString方法中 找到textScale  變量  更改其值變能更改顯示在cell上的字體的大小 

接着調用最后一句 self.attributedString = string  由於MyCell.m重寫了setAttributedString 方法所以調用之

 1 - (void)setAttributedString:(NSAttributedString *)attributedString
 2 {
 3     if (_attributedString != attributedString)
 4     {
 5         _attributedString = attributedString;
 6         
 7         // passthrough
 8         _attributedTextContextView.attributedString = _attributedString;
 9     }
10 }

很顯然 這里面的最后一句是最重要的代碼  只要設置DTAttributedTextContentView 的attributedString 為解析好的要顯示的

html   便能直接顯示出來

這里繼續單步跟蹤進入  會進入DTAttributedTextContentView 的一系列方法中  而這些方法是揭開 為什么沒有按照我門設計的布局顯示的線索

DTAttributedTextContentView.m

 1 - (void)setAttributedString:(NSAttributedString *)string
 2 {
 3     if (_attributedString != string)
 4     {
 5         
 6         _attributedString = [string copy];
 7         
 8         // new layout invalidates all positions for custom views
 9         [self removeAllCustomViews];
10         
11         [self relayoutText];
12     }
13 }

我門關心的是為什么我們設計的DTAttributedTextContentView  是在左邊的區域  但是 運行的時候卻充滿正的cell  這肯定與

最后一句代碼有關   relayoutText


繼續跟蹤  進入了最重要的方法  也是解決問題的關鍵方法  relayoutText

 

 1 - (void)relayoutText
 2 {
 3     // Make sure we actually have a superview before attempting to relayout the text.
 4     if (self.superview) {
 5         // need new layouter
 6         self.layouter = nil;
 7         self.layoutFrame = nil;
 8         
 9         // remove all links because they might have merged or split
10         [self removeAllCustomViewsForLinks];
11         
12         if (_attributedString)
13         {
14             // triggers new layout
15             
16             CGSize neededSize = [self sizeThatFits:self.bounds.size];
17            
18             // set frame to fit text preserving origin
19             // call super to avoid endless loop
20             [self willChangeValueForKey:@"frame"];
21             super.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, neededSize.width, neededSize.height);
22             
23             
24             [self didChangeValueForKey:@"frame"];
25         }
26         
27         [self setNeedsDisplay];
28         [self setNeedsLayout];
29     }
30 }

分析這段代碼 我們看出 

CGSize neededSize = [self sizeThatFits:self.bounds.size];

super.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, neededSize.width, neededSize.height);

這兩句是關鍵代碼    設置尺寸的話應該是在這兩句完成

首先第句話  得到了  neededSize(需求的尺寸  即顯示自己的完整html需要的尺寸) 

第二句 利用得到的neededSize 設置 super.frame  出問題 應該是在  第一句得到的 neededSize 有問題

經過NSlog 打印 neededSize 的值后  確實  是得到的neededSize.width為整個cell的 寬度 進而 設置super.frame 為整個cell的

寬度   

跟蹤第一句代碼  進入sizeThatFits方法

 1 - (CGSize)sizeThatFits:(CGSize)size
 2 {
 3     if (size.width==0)
 4     {
 5         size.width = self.bounds.size.width;
 6     }
 7     
 8     CGSize neededSize = CGSizeMake(size.width, CGRectGetMaxY(self.layoutFrame.frame) + edgeInsets.bottom);
 9     
10     
11     return neededSize;
12 }

我們注意到 sizeThatFits 這個方法 需要傳入一個參數(CGSize)   這里面  利用這個參數size.width 作為 

DTAttributedTextContentView 的寬度  也就是用來顯示html的DTAttributedTextContentView的寬度為size.width

然后根據這個寬度 以及html  計算出DTAttributedTextContentView的高度    計算高度的所有代碼全部在

CGRectGetMaxY(self.layoutFrame.frame) + edgeInsets.bottom  中   

我們只需要知道   如果我們傳入的size 的寬度越寬 計算出來的 高度會越小,反之亦然,最后得到的neededSize 包含了我們規定的寬度  以及計算的高度

在這里NSLog size.width 發現 傳入的寬度是 整個cell的寬度

回到上一層方法relayoutText 

CGSize neededSize = [self sizeThatFits:self.bounds.size];   傳入的值是 self.bounds.size

NSlog后確實 self.bounds.size.width 為整個cell的寬度

所以得到的 neededSize  寬度為整個cell的寬度  

最后設置super.frame  的時候 便充滿了整個cell


解決辦法:CGSize neededSize = [self sizeThatFits:self.bounds.size];這一句代碼傳入正確的size  可以去storyboard中看一下 自己設置的 DTAttributedTextContentView 的寬度  以及高度  然后創建一個size 傳入 方法中

最后要注意的一點

super.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, neededSize.width, neededSize.height);

改為

self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, neededSize.width, neededSize.height);

 

方法中的self.frame.origin.x =0;  所以 要想實現  我們設計的效果  這里不能為0   為0的話會緊挨着右邊   比如cell寬度

為1024    DTAttributedTextContentView 的寬度為 824   那么 用200  替代self.frame.origin.x即可

修改后的代碼

 1 - (void)relayoutText
 2 {
 3     // Make sure we actually have a superview before attempting to relayout the text.
 4     if (self.superview) {
 5         // need new layouter
 6         self.layouter = nil;
 7         self.layoutFrame = nil;
 8         //NSLog(@"%f",self.frame.size.width);
 9         // remove all links because they might have merged or split
10         [self removeAllCustomViewsForLinks];
11         
12         if (_attributedString)
13         {
14             // triggers new layout
15             CGSize size=CGSizeMake(612, 44);
16             
17             CGSize neededSize = [self sizeThatFits:size];
18            
19             // set frame to fit text preserving origin
20             // call super to avoid endless loop
21             [self willChangeValueForKey:@"frame"];
22           
23             self.frame=CGRectMake(156, self.frame.origin.y, neededSize.width, neededSize.height);
24             
25             [self didChangeValueForKey:@"frame"];
26         }
27         
28         [self setNeedsDisplay];
29         [self setNeedsLayout];
30     }
31 }

 

這樣便實現了我們的需求

 

 

截至到目前分析的這么多方法  只是從

1 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
2 {
3     MyCell *cell = (MyCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];
4      
5     return  cell.attributedTextContextView.frame.size.height+10;
6    
7     //return [cell requiredRowHeightInTableView:tableView];
8 }

 

中第一句代碼一路調用 深入分析的   

當第一句代碼最后深入調用結束  回到這里的時候 很顯然  我們已經計算出了neededSize 並且將

DTAttributedTextContentView 的frame的大小也設置為合適的大小  然后  下一句

return cell.attributedTextContextView.frame.size.height+10;

通過直接讀取 便可獲得合適的高度給cell  

到目前為止  甚至沒有執行

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

方法 但是 已經把所有的html顯示了一遍 

當tableView 運行到

1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
2 {
3     MyCell *cell = (MyCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];
4     
5     return cell;
6 }

方法時   與heightForRowAtIndexPath:  方法是記本相同的   只不過 這次在

MyCell *cell = (MyCell *)[self tableView:tableView preparedCellForIndexPath:indexPath];

方法中 cell不是創建出來的  而是直接從緩存中提取 的  這也是為什么  不會出現加載延遲的原因

 

原創博文,未經作者允許,不允許轉載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM