iOS新浪微博客户端开发(4)——自定义微博Cell的实现

首先看一下效果图(不带转发微博和带转发微博):

 

一、微博Cell布局分析

微博Cell的详细布局如下图所示,其主要控件有:头像、昵称、微博时间、来源、微博正文。如果有被转发微博,还包括被转发微博的整体框架、转发微博的用户昵称、转发微博的内容以及Cell下方的操作条(转发、评论、赞)等。

二、用户头像的封装——HeadIconView类

用过新浪微博APP的朋友们都知道,微博用户一共有4种认证:无认证、微博达人、个人vip、企业vip。针对不同的认证,微博会在用户头像的右下角显示不同的图标。同时,应用中不同的地方用户头像的尺寸大小也不尽相同,为了应用更好的扩展性和后期维护性,需要对用户头像进行封装。

用户头像的显示中包括头像、认证图标。HeadIconView类应继承自UIView,其持有User对象,并重写setUser方法,当HeadIconView拿到User对象后,根据User中的

profile_image_url等信息设置用户的头像、头像大小以及认证图标等。

HeadIconView.h

#import <UIKit/UIKit.h>

typedef enum{
    kHeadIconTypeSmall,
    kHeadIconTypeDefault,
    kHeadIconTypeBig
}HeadIconType;

@class User;
@interface HeadIconView : UIView
@property (strong, nonatomic)User *user;
@property (assign, nonatomic)HeadIconType headIconType;

+(CGSize)headIconSizeWithType:(HeadIconType)iconType;

-(void)setUser:(User *)user type:(HeadIconType)type;
@end

HeadIconView.m

#import "HeadIconView.h"
#import "Macro.h"
#import "User.h"
#import "HttpTool.h"

@interface HeadIconView()
{
    UIImageView *_headIcon;    // 头像图片
    UIImageView *_verifyIcon;  // 认证图标
    
    NSString *_placeHolder;    // 占位图片名称
}

@end

@implementation HeadIconView

-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 1. 用户头像图片
        _headIcon = [[UIImageView alloc]init];
        [self addSubview:_headIcon];
        
        // 2. 认证图标
        _verifyIcon = [[UIImageView alloc]init];
        [self addSubview:_verifyIcon];
    }
    
    return self;
}

-(void)setUser:(User *)user
{
    _user = user;
    
    // 1. 设置用户头像
    [HttpTool downloadImage:user.profile_image_url placeholderImage:[UIImage imageNamed:_placeHolder] imageView:_headIcon];
    
    // 2. 设置认证图标
    NSString *verifiedIcon = nil;
    switch (user.verified_type) {
        case kVerifiedTypeNone:
            _verifyIcon.hidden = YES;
            break;
            
        case kVerifiedTypeDaren:
            _verifyIcon.hidden = NO;
            verifiedIcon = @"avatar_grassroot.png";
            break;
        
        case kVerifiedTypePersonal:
            _verifyIcon.hidden = NO;
            verifiedIcon = @"avatar_vip.png";
            break;
            
        default:
            _verifyIcon.hidden = NO;
            verifiedIcon = @"avatar_enterprise_vip.png";
            break;
    }
    
    if (verifiedIcon) {
        _verifyIcon.hidden = NO;
        _verifyIcon.image = [UIImage imageNamed:verifiedIcon];
    }
}

-(void)setUser:(User *)user type:(HeadIconType)type
{
    self.headIconType = type;
    self.user = user;
}

#pragma mark 设置头像和认证图标的类型
-(void)setHeadIconType:(HeadIconType)headIconType
{
    _headIconType = headIconType;
    
    // 1. 判断认证类型
    CGSize headIconSize;
    
    switch (headIconType) {
        case kHeadIconTypeSmall:
            headIconSize = CGSizeMake(kHeadIconSmallW, kHeadIconSmallH);
            _placeHolder = @"avatar_default_small.png";
            break;
            
        case kHeadIconTypeDefault:
            headIconSize = CGSizeMake(kHeadIconDefaultW, kHeadIconDefaultH);
            _placeHolder = @"avatar_default.png";
            break;
            
        case kHeadIconTypeBig:
            headIconSize = CGSizeMake(kHeadIconBigW, kHeadIconBigH);
            _placeHolder =  @"avatar_default_big.png";
            break;
    }
    
    // 2. 设置frame
    _headIcon.frame = (CGRect){CGPointZero, headIconSize};
    _verifyIcon.bounds = CGRectMake(0, 0, kVerifyIconW, kVerifyIconH);
    _verifyIcon.center = CGPointMake(headIconSize.width, headIconSize.height);
    
    CGFloat width = headIconSize.width + kVerifyIconW*0.5;
    CGFloat height = headIconSize.height + kVerifyIconH*0.5;
    self.bounds = CGRectMake(0, 0, width, height);
}

#pragma mark 设置头像的大小
+(CGSize)headIconSizeWithType:(HeadIconType)iconType
{
    CGSize headIconSize;
    
    switch (iconType) {
        case kHeadIconTypeSmall:
            headIconSize = CGSizeMake(kHeadIconSmallW, kHeadIconSmallH);
            break;
            
        case kHeadIconTypeDefault:
            headIconSize = CGSizeMake(kHeadIconDefaultW, kHeadIconDefaultH);
            break;
        
        case kHeadIconTypeBig:
            headIconSize = CGSizeMake(kHeadIconBigW, kHeadIconBigH);
            break;
    }
    
    CGFloat width = headIconSize.width + kVerifyIconW * 0.5;
    CGFloat height = headIconSize.height + kVerifyIconH*0.5;
    
    return CGSizeMake(width, height);
}

@end

三、微博配图的封装(ImageItemView和ImageListView)

当一条微博有配图时,我们需要判断微博配图的数目并将这些配图以正确的方式显示出来。

这些配图可能是Gif图片或普通静态图片,当拿到微博配图数据时,要判断其图片类型,并设置相应的gif标志。首先对单个配图进行封装,ImageItemView类设置一个NSString类型的对象——imageUrl,并重写setImageUrl方法,当拿到图片的URL时,自动加载图片并判断图片是否gif。

ImageItemView.h

#import <UIKit/UIKit.h>

@interface ImageItemView : UIImageView

@property (copy, nonatomic)NSString *imageUrl;

@end

ImageItemView.m
#import "ImageItemView.h"
#import "HttpTool.h"

@interface ImageItemView()
{
    UIImageView *_gifFlagImage;
}

@end

@implementation ImageItemView

-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        _gifFlagImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"timeline_image_gif.png"]];
        [self addSubview:_gifFlagImage];
    }
    return self;
}

-(void)setImageUrl:(NSString *)imageUrl
{
    _imageUrl = imageUrl;
    
    // 1. 加载图片
    [HttpTool downloadImage:imageUrl placeholderImage:[UIImage imageNamed:@"timeline_image_loading.png"] imageView:self];
    
    // 2. 判断该图片是否时gif
    _gifFlagImage.hidden = ![imageUrl.lowercaseString hasSuffix:@"gif"];
}

-(void)setFrame:(CGRect)frame
{
    // 设置gifView的位置
    CGRect gifViewFrame = _gifFlagImage.frame;
    gifViewFrame.origin.x = frame.size.width - gifViewFrame.size.width;
    gifViewFrame.origin.y = frame.size.height - gifViewFrame.size.height;
    _gifFlagImage.frame = gifViewFrame;
    
    [super setFrame:frame];
}

@end

由于微博的配图数目范围为0~9,对于不同数目的配图单独处理其布局显然是不合适的,因此,需要也需要对配图类进行封装。ImageListView类中有一个NSArray类型的对象用于接收外部传入的微博配图信息(配图的URL),并根据配图的数目自动确定显示配图控件的CGSize大小。

ImageListView.h

#import <UIKit/UIKit.h>

@interface ImageListView : UIView
@property (strong, nonatomic)NSArray *imageUrlList;

+(CGSize)imageListSizeWithCount:(int)count;
@end
ImageListView.m
#define kCount 9

#define kSingleImageW 120
#define kSingleImageH 120
#define kMultiImageW 80
#define kMultiImageH 80

#define kMargin 5

#import "ImageListView.h"
#import "UIImageView+WebCache.h"
#import "ImageItemView.h"

@implementation ImageListView

-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 先把有可能显示的空间都加入
        for (int i = 0; i < kCount; i++) {
            ImageItemView *imageView = [[ImageItemView alloc]init];
            [self addSubview:imageView];
        }
    }
    
    return  self;
}

-(void)setImageUrlList:(NSArray *)imageUrlList
{
    _imageUrlList = imageUrlList;
    int count = (int)imageUrlList.count;
    
    for (int i = 0; i < kCount; i++) {
        // 1. 取出对应位置的子控件
        ImageItemView *child = self.subviews[i];
        
        // 2. 判断配图的数量
        if (i >= count) {
            child.hidden = YES; // 多余的位置隐藏
        }else{
            child.hidden = NO;
            
            // 3. 设置图片
            child.imageUrl = imageUrlList[i][@"thumbnail_pic"];
            
            // 4. 设置frame
            if (count == 1) {
                child.contentMode = UIViewContentModeScaleAspectFit; // 自适应图片的大小
                child.frame = CGRectMake(0, 0, kSingleImageW, kSingleImageH);
            }else{
                int temp = (count == 4 ? 2 : 3);
                int row = i / temp;
                int column = i % temp;
                
                CGFloat x = (kMultiImageW + kMargin)*column;
                CGFloat y = (kMultiImageH + kMargin)*row;
                child.frame = CGRectMake(x, y, kMultiImageW, kMultiImageH);
                child.contentMode = UIViewContentModeScaleAspectFill; // 图片适应内容拉伸
                child.clipsToBounds = YES; // 剪去图片的多余部分
                
            }
        }
    }
}

+(CGSize)imageListSizeWithCount:(int)count
{
    // 1. 只有一张配图
    if (count == 1) {
        return CGSizeMake(kSingleImageW, kSingleImageH);
    }
    
    // 2. 至少有两张配图
    int countRow = ((count == 4) ? 2 : 3);
    
    // 总行数
    int rows = (count + countRow - 1) / countRow;
    
    // 总列数
    int columns = (count >= 3 ? 3 : 2);
    
    CGFloat width = columns * kMultiImageW + (columns - 1) * kMargin;
    CGFloat height = rows * kMultiImageH + (rows - 1) * kMargin;
    
    return CGSizeMake(width, height);
}

@end

四、微博底部状态条(CellOptionBar)

状态条中得数据是由微博(Status)的信息得到的,CellOptionBar持有Status对象,并用status中的repost_count、comments_count和attitudes_count设置button上显示的数字。

CellOptionBar.h

#import <UIKit/UIKit.h>

@class Status;
@interface CellOptionBar : UIImageView

@property (strong, nonatomic)Status *status;

@end


CellOptionBar.m
#import "CellOptionBar.h"
#import "Status.h"
#import "UIImage+Extended.h"
#import "NSString+Extended.h"
#import "Macro.h"

@interface CellOptionBar()
{
    UIButton *_repostButton;
    UIButton *_commentButton;
    UIButton *_attitudeButton;
}

@end

@implementation CellOptionBar

-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 设置自动伸缩,粘在cell的底部
        //self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
        
        // 设置可以接收用户点击
        self.userInteractionEnabled = YES;
        
        // 设置背景
        self.image = [UIImage resizedImage:@"timeline_card_background.png"];
        self.highlightedImage = [UIImage resizedImage:@"timeline_card_background_highlighted.png"];
        
        // 添加底部的三个按钮
        _repostButton = [self addButtonWithTitle:@"转发" iconImage:@"timeline_icon_retweet.png" backgroundImage:@"timeline_card_middle_background.png"buttonIndex:0];
        _commentButton = [self addButtonWithTitle:@"评论" iconImage:@"timeline_icon_comment.png" backgroundImage:@"timeline_card_middle_background.png" buttonIndex:1];
        _attitudeButton = [self addButtonWithTitle:@"赞" iconImage:@"timeline_icon_like_disable.png" backgroundImage:@"timeline_card_middle_background.png" buttonIndex:2];
    }
    
    return self;
}

-(UIButton *)addButtonWithTitle:(NSString*)title iconImage:(NSString*)iconImage backgroundImage:(NSString*)bgImage buttonIndex:(NSInteger)index
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    
    // 标题
    [button setTitle:title forState:UIControlStateNormal];
    
    // 图标
    [button setImage:[UIImage imageNamed:iconImage] forState:UIControlStateNormal];
    
    // 背景图片
    [button setBackgroundImage:[UIImage resizedImage:bgImage] forState:UIControlStateNormal];
    [button setBackgroundImage:[UIImage resizedImage:[bgImage fileNameAppend:@"_highlighted"]] forState:UIControlStateHighlighted];
    
    // 文字颜色
    [button setTitleColor:[UIColor colorWithWhite:0.647 alpha:1.000] forState:UIControlStateNormal];
    button.titleLabel.font = [UIFont systemFontOfSize:12];
    
    // 设置按钮的frame
    CGFloat width = self.frame.size.width / 3;
    button.frame = CGRectMake(width * index, 0, width, kCellBarHeight);
    
    // 设置文字左边与图片的间距
    button.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 0);
    
    [self addSubview:button];
    
    if (index) {
        UIImage * dividerImage = [UIImage imageNamed:@"timeline_card_bottom_line.png"];
        UIImageView *divider = [[UIImageView alloc]initWithImage:dividerImage];
        divider.center = CGPointMake(button.frame.origin.x, kCellBarHeight * 0.5);
        
        [self addSubview:divider];
    }
    
    return button;
}

-(void)setButton:(UIButton*)button withTitle:(NSString*)title withCount:(int)count
{
    if (count >= 10000) {
        NSString *title = [NSString stringWithFormat:@"%.1f万", count / 10000.0];
        title = [title stringByReplacingOccurrencesOfString:@".0" withString:@""];
        [button setTitle:title forState:UIControlStateNormal];
    }else if (count > 0){
        [button setTitle:[NSString stringWithFormat:@"%d", count] forState:UIControlStateNormal];
    }else{
        [button setTitle:title forState:UIControlStateNormal];
    }

}

-(void)setFrame:(CGRect)frame
{
    frame.size.width = [UIScreen mainScreen].bounds.size.width;
    frame.size.height = kCellBarHeight;
    [super setFrame:frame];
}

-(void)setStatus:(Status *)status
{
    _status = status;
    
    // 1. 转发
    [self setButton:_repostButton withTitle:@"转发" withCount:status.repost_count];
    
    // 2. 评论
    [self setButton:_commentButton withTitle:@"评论" withCount:status.comments_count];
    
    // 3. 赞
    [self setButton:_attitudeButton withTitle:@"赞" withCount:status.attitudes_count];
}

@end

五、Cell及内部frame的计算

首先,StatusCellFrame类负责计算每条微博中各个子控件的frame,它有一个Status类型的变量,拿到微博的详细数据后开始计算frame;

StatusCell持有StatusCellFrame对象,使用cell.statusCellFrame设置各个Cell的frame。

StatusCellFrame.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class Status;
@interface StatusCellFrame : NSObject
{
    CGFloat _cellHeight;
    CGRect _repostFrame; // 被转发微博的父控件
}

@property (strong, nonatomic)Status *status;  // 微博数据

@property (readonly, nonatomic) CGFloat cellHeight;

@property (readonly, nonatomic) CGRect headIconFrame; // 头像的frame
@property (readonly, nonatomic) CGRect screenNameFrame;
@property (readonly, nonatomic) CGRect mbIconFrame;
@property (readonly, nonatomic) CGRect timeFrame;
@property (readonly, nonatomic) CGRect sourceFrame;
@property (readonly, nonatomic) CGRect textFrame;
@property (readonly, nonatomic) CGRect imageFrame;

@property (readonly, nonatomic) CGRect repostedFrame; // 被转发微博的父控件
@property (readonly, nonatomic) CGRect repostedScreenNameFrame;
@property (readonly, nonatomic) CGRect repostedTextFrame;
@property (readonly, nonatomic) CGRect repostedImageFrame;

@property (readonly, nonatomic) CGRect cellBarFrame;
@property (readonly, nonatomic) CGRect blankBarFrame;

@end

StatusCellFrame.m

#import "StatusCellFrame.h"
#import "Status.h"
#import "User.h"
#import "HeadIconView.h"
#import "ImageListView.h"
#import "Macro.h"

@implementation StatusCellFrame

-(void)setStatus:(Status *)status
{
    _status = status;
    
    // 拿到微博数据即计算其内部各控件的frame
    
    // 整个cell的宽度
    CGFloat cellWidth = [UIScreen mainScreen].bounds.size.width;
    
    // 1. 头像
    CGFloat headIconX = kCellBorderWidth_Cell;
    CGFloat headIconY = kCellBorderWidth_Cell;
    CGSize iconSize = [HeadIconView headIconSizeWithType:kHeadIconTypeSmall];
    _headIconFrame = CGRectMake(headIconX, headIconY, iconSize.width, iconSize.height);
    
    // 2. 昵称
    CGFloat screenNameX = CGRectGetMaxX(_headIconFrame) + kCellBorderWidth;
    CGFloat screenNameY = headIconY;
    CGSize screenNameSize = [status.user.screenName sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kGlobalTitleSizeBig]}];
    _screenNameFrame = (CGRect){{screenNameX, screenNameY}, screenNameSize};
    
    // 3. 会员图标
    if (status.user.mbType != kMBTypeNone) {
        CGFloat mbIconX = CGRectGetMaxX(_screenNameFrame) + kCellBorderWidth;
        CGFloat mbIconY = screenNameY + (screenNameSize.height - kMBIconH)*0.5;
        _mbIconFrame = CGRectMake(mbIconX, mbIconY, kMBIconW, kMBIconH);
    }
    
    // 4. 时间
    CGFloat timeX = screenNameX;
    CGFloat timeY = CGRectGetMaxY(_screenNameFrame) + kCellBorderWidth;
    CGSize timeSize = [status.create_at sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kGlobalTitleSizeSuperSmall]}];
    _timeFrame = (CGRect) {{timeX, timeY}, timeSize};
    
    // 5. 微博来源
    CGFloat sourceX = CGRectGetMaxX(_timeFrame) + kCellBorderWidth;
    CGFloat sourceY = timeY;
    CGSize sourceSize = [status.source sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kGlobalTitleSizeSuperSmall]}];
    _sourceFrame = (CGRect){{sourceX, sourceY}, sourceSize};
    
    // 6. 微博内容
    CGFloat textX = headIconX;
    CGFloat maxY = MAX(CGRectGetMaxY(_sourceFrame), CGRectGetMaxY(_headIconFrame));
    CGFloat textY = maxY + kCellBorderWidth;
    CGSize textSize = [status.statusContent boundingRectWithSize:CGSizeMake(cellWidth - 2*kCellBorderWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kGlobalTitleSizeBig]} context:nil].size;
    _textFrame = (CGRect) {{textX, textY}, textSize};
    
    if (status.picUrls.count) {// 7. 判断是否有配图
        CGFloat imageX = textX;
        CGFloat imageY = CGRectGetMaxY(_textFrame) + kCellBorderWidth;
        CGSize imageSize = [ImageListView imageListSizeWithCount:(int)status.picUrls.count];
        _imageFrame = CGRectMake(imageX, imageY, imageSize.width, imageSize.height);
    }else if (status.repostStatus){// 8. 有转发的微博
        // 被转发微博的整体框架
        CGFloat repostedX = 2;
        CGFloat repostedY = CGRectGetMaxY(_textFrame) + kCellBorderWidth;
        CGFloat repostedWidth = cellWidth - 4;
        CGFloat repostedHeight = 0;
        
        // 9. 被转发微博的昵称
        CGFloat repostedScreenNameX = kCellBorderWidth_Cell;
        CGFloat repostedScreenNameY = kCellBorderWidth_Cell;
        NSString *name = [NSString stringWithFormat:@"@%@", status.repostStatus.user.screenName];
        CGSize repostedScreenNameSize = [name sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kGlobalTitleSizeMiddle]}];
        _repostedScreenNameFrame = (CGRect){{repostedScreenNameX, repostedScreenNameY}, repostedScreenNameSize};
        
        // 9. 被转发微博的内容
        CGFloat repostedTextX = repostedScreenNameX;
        CGFloat repostedTextY = CGRectGetMaxY(_repostedScreenNameFrame) + kCellBorderWidth;
        CGSize repostedTextSize = [status.repostStatus.statusContent boundingRectWithSize:CGSizeMake(repostedWidth - 2*kCellBorderWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kGlobalTitleSizeMiddle]} context:nil].size;
        _repostedTextFrame = (CGRect){{repostedTextX, repostedTextY}, repostedTextSize};
        
        // 10. 被转发微博的配图
        if (status.repostStatus.picUrls.count) {
            CGFloat repostedImageX = repostedTextX;
            CGFloat repostedImageY = CGRectGetMaxY(_repostedTextFrame) + kCellBorderWidth;
            CGSize imageSize = [ImageListView imageListSizeWithCount:(int)status.repostStatus.picUrls.count];
            _repostedImageFrame = CGRectMake(repostedImageX, repostedImageY, imageSize.width, imageSize.height);
            
            // 11. 计算被转发微博的整体高度
            repostedHeight += CGRectGetMaxY(_repostedImageFrame) + kCellBorderWidth;
        }else{
            repostedHeight += CGRectGetMaxY(_repostedTextFrame) + kCellBorderWidth;
        }
        
        _repostedFrame = CGRectMake(repostedX, repostedY, repostedWidth, repostedHeight);
    }
    
    // 12. 整个cell的高度
    _cellHeight = kCellBarHeight + kCellMargin;
    //_cellHeight = kCellBorderWidth + kCellMargin;
    if (status.picUrls.count) {
        _cellHeight += CGRectGetMaxY(_imageFrame) + kCellBorderWidth;
    }else if (status.repostStatus){
        _cellHeight += CGRectGetMaxY(_repostedFrame);
    }else{
        _cellHeight += CGRectGetMaxY(_textFrame) + kCellBorderWidth;
    }
    
    // 底部操作条
    CGFloat cellbarX = 0;
    CGFloat cellbarY = _cellHeight - kCellBarHeight - kCellMargin;
    _cellBarFrame = CGRectMake(cellbarX, cellbarY, cellWidth, kCellBarHeight);
    
    // 底部间距栏
    CGFloat blankX = 0;
    CGFloat blankY = _cellHeight - kCellMargin;
    CGFloat blankWidth = cellWidth;
    CGFloat blankHeight = 10;
    _blankBarFrame = CGRectMake(blankX, blankY, blankWidth, blankHeight);
}

@end

StatusCell.h

#import <UIKit/UIKit.h>

@class StatusCellFrame;
@class StatusCell;

@protocol StatusCellDelegate <NSObject>
@optional
-(void)StatusTap:(StatusCell *)cell;
-(void)RepostStatusTap:(StatusCell *)cell;

@end

@interface StatusCell : UITableViewCell
{
    UIImageView *_background;
    UIImageView *_selectedBackground;
    
    UIImageView *_reposted; // 被转发的微博的父控件
}

@property (strong, nonatomic)StatusCellFrame *cellFrame;

@property (assign, nonatomic)id<StatusCellDelegate> delegate;

@end

StatusCell.m

#import "StatusCell.h"
#import "HeadIconView.h"
#import "ImageListView.h"
#import "UIImage+Extended.h"
#import "Status.h"
#import "User.h"
#import "StatusCellFrame.h"
#import "Macro.h"
#import "CellOptionBar.h"

@interface StatusCell()
{
    HeadIconView *_headIcon;          // 头像
    UILabel *_screenName;             // 昵称
    UIImageView *_mbIcon;             // 会员图标
    UILabel *_time;                   // 时间
    UILabel *_source;                 // 来源
    UILabel *_statusContent;          // 微博内容
    ImageListView *_images;           // 微博配图
    
    UILabel *_repostedScreenName;     // 转发微博用户昵称
    UILabel *_repostedStatusContent;  // 转发微博内容
    ImageListView *_repostedImages;   // 转发微博配图
    
    CellOptionBar *_cellBar;
    UIImageView *_blankBar;
}

@end

@implementation StatusCell

-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        
        // 1. 添加当前微博的子控件
        [self addAllSubViews];
        
        // 2. 添加被转发微博的子控件
        [self addRepostAllSubviews];
        
        // 3. 设置背景
        [self setBackground];
        
        // 4. 底部的操作条(转发、评论、赞)
        _cellBar = [[CellOptionBar alloc]init];
        [self.contentView addSubview:_cellBar];
    }
    
    return self;
}

#pragma mark 设置cell的背景
-(void)setBackground
{
    self.backgroundColor = [UIColor clearColor];
    
    // 1. 默认背景
    UIImageView *background = [[UIImageView alloc]init];
    self.backgroundView = background;
    _background = background;
    _background.image = [UIImage resizedImage:@"common_card_background.png"];
    
    // 2. 长按背景
    UIImageView *selectedBackground = [[UIImageView alloc]init];
    self.selectedBackgroundView = selectedBackground;
    _selectedBackground = selectedBackground;
    _selectedBackground.image = [UIImage resizedImage:@"common_card_background_highlighted.png"];
}

#pragma mark 添加cell中的子控件
-(void)addAllSubViews
{
    // 1. 头像
    _headIcon = [[HeadIconView alloc]init];
    [self.contentView addSubview:_headIcon];
    
    // 2. 昵称
    _screenName = [[UILabel alloc]init];
    _screenName.font = kScreenNameFont;
    _screenName.backgroundColor = [UIColor clearColor];
    [self.contentView addSubview:_screenName];
    
    // 3. 会员图标
    _mbIcon = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"common_icon_membership.png"]];
    [self.contentView addSubview:_mbIcon];
    
    // 4. 时间
    _time = [[UILabel alloc]init];
    _time.font = kTimeFont;
    _time.textColor = kColor(246, 165, 68);
    _time.backgroundColor = [UIColor clearColor];
    [self.contentView addSubview:_time];
    
    // 5. 来源
    _source = [[UILabel alloc]init];
    _source.font = kSourceFont;
    _source.backgroundColor = [UIColor clearColor];
    [self.contentView addSubview:_source];
    
    // 6. 微博内容
    _statusContent = [[UILabel alloc]init];
    _statusContent.font = kStatusContentFont;
    _statusContent.numberOfLines = 0;  // 换行
    _statusContent.backgroundColor = [UIColor clearColor];
    [self.contentView addSubview:_statusContent];
    
    // 7. 微博配图
    _images = [[ImageListView alloc]init];
    [self.contentView addSubview:_images];
    
    // 8. cell间距图片
    _blankBar = [[UIImageView alloc]init];
    _blankBar.image = [UIImage resizedImage:@"timeline_card_bottom_background_highlighted.png"];
    //_blankBar.backgroundColor = kGlobalBackgroundColor;
    [self.contentView addSubview:_blankBar];
}

#pragma mark 添加被转发的微博的子控件
-(void)addRepostAllSubviews
{
    UITapGestureRecognizer *repostStatusTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(repostStatusTap)];
    
    // 1. 被转发微博的父控件
    _reposted = [[UIImageView alloc]init];
    _reposted.image = [UIImage resizedImage:@"timeline_retweet_background.png"];
    _reposted.userInteractionEnabled = YES;
    [_reposted addGestureRecognizer:repostStatusTap];
    [self.contentView addSubview:_reposted];
    
    // 2. 被转发微博的用户昵称
    _repostedScreenName = [[UILabel alloc]init];
    _repostedScreenName.font = kRepostedScreenNameFont;
    _repostedScreenName.textColor = kRepostScreenNameColor;
    _repostedScreenName.backgroundColor = [UIColor clearColor];
    [_reposted addSubview:_repostedScreenName];
    
    // 3. 被转发微博的内容
    _repostedStatusContent = [[UILabel alloc]init];
    _repostedStatusContent.font = kRepostedTextFont;
    _repostedStatusContent.numberOfLines = 0;
    _repostedStatusContent.backgroundColor = [UIColor clearColor];
    [_reposted addSubview:_repostedStatusContent];
    
    // 4. 被转发微博的配图
    _repostedImages = [[ImageListView alloc]init];
    [_reposted addSubview:_repostedImages];
}

-(void)setCellFrame:(StatusCellFrame *)cellFrame
{
    _cellFrame = cellFrame;
    
    Status *s = cellFrame.status;
    
    // 1. 头像
    [_headIcon setUser:s.user type:kHeadIconTypeSmall];
    _headIcon.frame = cellFrame.headIconFrame;
    
    // 2. 昵称
    _screenName.frame = cellFrame.screenNameFrame;
    _screenName.text = s.user.screenName;
    
    // 2.1 判断是否会员
    if (s.user.mbType == kMBTypeNone) {
        _screenName.textColor = kScreenNameColor;
        _mbIcon.hidden = YES;
    }else{
        _screenName.textColor = kMBScreenNameColor;
        _mbIcon.hidden = NO;
        _mbIcon.frame = cellFrame.mbIconFrame;
    }
    
    // 3. 时间
    _time.text = s.create_at;
    CGFloat timeX = cellFrame.screenNameFrame.origin.x;
    CGFloat timeY = CGRectGetMaxY(cellFrame.screenNameFrame) + kCellBorderWidth;
    CGSize timeSize = [_time.text sizeWithAttributes:@{NSFontAttributeName:kTimeFont}];
    _time.frame = (CGRect){{timeX, timeY}, timeSize};
    
    // 4. 来源
    _source.text = s.source;
    CGFloat sourceX = CGRectGetMaxX(_time.frame) + kCellBorderWidth;
    CGFloat sourceY = timeY;;
    CGSize sourceSize = [_source.text sizeWithAttributes:@{NSFontAttributeName:kSourceFont}];
    _source.frame = (CGRect){{sourceX, sourceY}, sourceSize};
    
    // 5. 内容
    _statusContent.frame = cellFrame.textFrame;
    _statusContent.text = s.statusContent;
    
    // 6. 微博配图
    if (s.picUrls.count) {
        _images.hidden = NO;
        _images.frame = cellFrame.imageFrame;
        _images.imageUrlList = s.picUrls;
    }else{
        _images.hidden = YES;
    }
    
    // 7. 被转发的微博
    if (s.repostStatus) {
        
        // 父控件是否隐藏
        _reposted.hidden = NO;
        _reposted.frame = cellFrame.repostedFrame;
        
        // 昵称
        _repostedScreenName.frame = cellFrame.repostedScreenNameFrame;
        _repostedScreenName.text = [NSString stringWithFormat:@"@%@", s.repostStatus.user.screenName];

        // 微博内容
        _repostedStatusContent.frame = cellFrame.repostedTextFrame;
        _repostedStatusContent.text = s.repostStatus.statusContent;
        
        // 是否有配图
        if (s.repostStatus.picUrls.count) {
            _repostedImages.hidden = NO;
            _repostedImages.frame = cellFrame.repostedImageFrame;
            _repostedImages.imageUrlList = s.repostStatus.picUrls;
        }else{
            _repostedImages.hidden = YES;
        }
    }else{
        _reposted.hidden = YES;
    }
    
    // 8. 底部操作条
    _cellBar.status = cellFrame.status;
    _cellBar.frame = cellFrame.cellBarFrame;
    
    
    _blankBar.frame = cellFrame.blankBarFrame;
}

#pragma mark 外部微博的点击事件
-(void)statusTap
{
    if (_delegate && [_delegate respondsToSelector:@selector(StatusTap:)]) {
        [_delegate StatusTap:self];
    }
}

#pragma mark 被转发微博的点击事件
-(void)repostStatusTap
{
    if (_delegate && [_delegate respondsToSelector:@selector(RepostStatusTap:)]) {
        [_delegate RepostStatusTap:self];
    }
}


@end

下面看看StatusCell类如何使用:

// 获取最新的微博数据
    [StatusTool statusesWithSinceId:sinceId maxId:0 success:^(NSArray *statuses) {
        // 1. 拿到最新的微博数据时,计算它的frame
        NSMutableArray *newFrames = [NSMutableArray array];
        for (Status *s in statuses) {
            StatusCellFrame *cellFrame = [[StatusCellFrame alloc]init];
            cellFrame.status = s;
            
            [newFrames addObject:cellFrame];
        }
        
        // 2. 将newFrames整体插入到旧数据前面
        [_statusFrames insertObjects:newFrames atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newFrames.count)]];
        
        // 3. 刷新表格
        [self.tableView reloadData];
        
        // 4. 结束刷新
        [self.tableView headerEndRefreshing];
        
        // 5. 在顶部展示最新微博的数目
        [self showNewStatusCount:(int)statuses.count];
        
#warning 播放声音不应该在这里
        // 播放声音
        if (_wavPath) {
            NSURL *soundURL = [NSURL fileURLWithPath:_wavPath];
            _audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:soundURL error:nil];
            [_audioPlayer play];
        }
    } failure:^(NSError *err) {
        // 结束刷新
        [self.tableView headerEndRefreshing];
    }];

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    
    StatusCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    // Configure the cell...
    if (cell == nil) {
        cell = [[StatusCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        cell.delegate = self;
    }
    
    cell.cellFrame = _statusFrames[indexPath.row];
    
    return cell;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值