• <原>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不是创建出来的  而是直接从缓存中提取 的  这也是为什么  不会出现加载延迟的原因

    原创博文,未经作者允许,不允许转载

  • 相关阅读:
    多级别过滤器
    MongoDBAppender
    org.slf4j.impl.SimpleLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext
    简单引入logback
    Logback configuration
    PatternLayoutEncoder 输出格式
    ConsoleAppender
    FileAppender
    Linux学习笔记
    GitLab CI/CD 学习记录
  • 原文地址:https://www.cnblogs.com/bucengyongyou/p/2668006.html
Copyright © 2020-2023  润新知