前言
做一个微博客户端的第三方是自学的第一个实践的项目,自从从事iOS工作之后,就把这个项目给搁置了。趁现在过年回来有些空闲时间,再次修改(总觉得项目就是不停地修改)。并且记录一点东西,以后可再回头看看从前走过的路,挖过的坑。这是一条微博的展示,不是整个项目。
废话不多说,先上效果图:
拆分控件
在开始动手写代码之前,我们得先确定怎么去实现这样子的布局,也就是分析需要用到哪些控件。
观察微博客户端,整体是可滑动的,而且界面展示比较规律的,所以应该是使用UITableView实现的。那么一条微博应该是用UITableViewCell 来实现的,这个由点击时,整条微博都变色可以肯定。
一条微博与其他的微博之间是有大约10PX的间距,可以认为每个Section就只有一个Cell。
每条微博的共同部分包括:头像,用户名称,发布时间与发布来源,微博正文,底部的转发,评论,赞。不同的部分有:配图,非原创微博的正文。(视频,文章等在这个项目中不做考虑)所以共同部分可以直接在xib上固定,不同部分则需要在.m文件用代码来写。
控件的确定:头像和配图使用UIImageView,用户名称,发布时间与来源,微博正文,非原创微博的正文都是使用UILabel,而底部的转发,评论,赞使用UIButton。
当一条微博是非原创微博(转发微博),根据点击被转发的微博的变色情况,可以确定转发微博是一个整体,可以确定转发微博是放在一个UIView上再添加到Cell上面的。
布局
放上一张xib的布局图:(button是与底部进行约束的)
共同的部分,先设置一些参数。
- (void)awakeFromNib {[super awakeFromNib];_contentLabel.numberOfLines = 0;//正文多行//圆形头像_headImageView.layer.masksToBounds = YES;_headImageView.layer.cornerRadius = HeadImageHeight / 2;//设置tag,为了后面识别点击哪个按钮_repostButton.tag = RepostButtonTag;_commentButton.tag = CommentButtonTag;_likeButton.tag = LikeButtonTag;}
先说配图,微博的配图最多9张。首先先根据配图的张数和屏幕的宽度确定图片的大小imageWidth,然后再确定行数和列数。
1、只有一张配图时,imageWidth = 屏幕宽度 * 0.55;
2、配图超过一张时,imageWidth = (屏幕宽度 - 间隙) / 3;
3、配图是2张或者4张时,分为两列布局,而配图3张或者大于4张时,则分为三列布局。
LeadingSpace 是图片与两侧屏幕的间隙,为8PX, ImageSpace是图片之间的间隙为4PX。UI_SCREEN_WIDTH是屏幕宽度。
//根据图片数量获得列数 if (_imageArray.count == 1) { //一列 column = 1; imageWidth = UI_SCREEN_WIDTH * 0.55; } else if (_imageArray.count == 2 || _imageArray.count == 4) { //两列 column = 2; imageWidth = (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * 2) / 3; } else { //三列 column = 3; imageWidth = (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * 2) / 3; } //根据图片的数量和列数获得行数 if (_imageArray.count % column == 0) { row = _imageArray.count / column; } else { row = _imageArray.count / column + 1; }
确定了配图的大小,再根据位置,就可以创建UIImageView。 配图的位置则在正文起始位置 + 正文的高度 + 间隙,而获取正文的高度由以下方法来完成:
* 计算label的高度 * * @param text 文字 * @param width label宽度 * @param font 字体 * * @return label高度+ (CGFloat)getLabelHeightWithText:(NSString *)text width:(CGFloat)width font:(UIFont *)font {CGSize size = CGSizeMake(width, MAXFLOAT);//设置一个行高的上限CGSize returnSize;NSDictionary *attribute = @{ NSFontAttributeName : font };returnSize = [text boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attribute context:nil].size;return returnSize.height;}
对于原创微博正文的起始位置可以由xib看出来,头像的高度固定为48,而上下的间隙为8, 则起始位置Y坐标为 48 + 16 = 64;而对于非原创微博,正文的起始位置Y坐标为8 (此处的8是相对于配图的父容器UIView的位置,对于非原创微博而言,更重要的是计算出父容器UIView在Cell中的位置);
然后根据配图的位置和大小创建UIImageView,如下图,其中originY为第一张配图的起始位置的Y坐标。
//根据位置创建imageViewfor (int i = 0; i < row; i++) { for (int j = 0; j < column; j++) { //用来判断数据是否越界 if (i * column + j < _imageArray.count) { imageUrl = _imageArray[i * column + j]; if (imageUrl) { TapImageView *imageView = [[TapImageView alloc] initWithFrame:CGRectMake(LeadingSpace + j * (ImageSpace + imageWidth), originY + LeadingSpace + i * (ImageSpace + imageWidth), imageWidth, imageWidth)]; imageView.tag = ImageViewTag + i * column + j; //block通知,点击了图片,展示大图 __weak typeof(self) weakSelf = self; imageView.didTouchImage = ^(NSInteger index) { [weakSelf showFullScreenImage:index]; }; [imageView setImageUrl:imageUrl index:i * column + j]; //原创微博直接添加的cell中,非原创则加入一个容器中UIView,再将容器加入cell中 if (isForward) { [_forwardedContainerView addSubview:imageView]; } else { [self addSubview:imageView]; } } } else { //越界后跳出for循环 break; } }}
TapImageView是UIImageView的子类,主要是添加了手势,实现点击图片展开大图的效果,后面再做详细介绍。
非原创微博有两个正文,分别用“上文”和“下文”来区分吧。上文已经在xib中,而下文和配图是放在_forwardedContainerView(UIView)中,然后再添加到Cell中的,所以要计算它的起始位置Y坐标。上文的Y坐标已经确定为64了,而_forwardedContainerView与上文之间的间隙为8,所以下文的Y坐标 = 64 + 上文的高度 + 8。其中ContentLabelOriginY = 64
CGFloat contentHeight = [FitBoUI getLabelHeightWithText:_weibo.text width:UI_SCREEN_WIDTH - LeadingSpace * 2 font:FontSize12];CGFloat originY = ContentLabelOriginY + contentHeight;originY += LeadingSpace;_forwardedContainerView = [[UIView alloc] initWithFrame:CGRectMake(0, originY, UI_SCREEN_WIDTH, 40)];_forwardedContainerView.tag = ForwardedContainerViewTag;_forwardedContainerView.backgroundColor = [UIColor colorWithWhite:0.75 alpha:0.35];//添加单击手势,点击原创微博,进入该微博的详情页面[self forwardedContainerViewAddGesture];[self addSubview:_forwardedContainerView];
_forwardedContainerView的高度是随便给的,需要在计算实际高度之后再重新赋值。
//下文是用户名称和文字拼凑而来。NSString *forwardText = [NSString stringWithFormat:@"@%@:%@", forwardWeibo.user.name, forwardWeibo.text];CGFloat forwardContentHeight = [FitBoUI getLabelHeightWithText:forwardText width:UI_SCREEN_WIDTH - LeadingSpace * 2 font:FontSize12];UILabel *forwardedContentLabel = [[UILabel alloc] initWithFrame:CGRectMake(LeadingSpace, LeadingSpace, UI_SCREEN_WIDTH - LeadingSpace * 2, forwardContentHeight)];forwardedContentLabel.font = FontSize12;forwardedContentLabel.numberOfLines = 0;forwardedContentLabel.text = forwardText;[_forwardedContainerView addSubview:forwardedContentLabel];//创建imageview,并根据修改实际高度,pic_urls是图片的网址数组。得到的imageHeight为所有图片以及图片之间的间隙总和。CGFloat imageHeight = [self initImageView:forwardWeibo.pic_urls originY:forwardContentHeight + LeadingSpace isForward:YES];//此处无论有没有配图,都预留了配图上下两个间隙的高度。所以,如果没有配图,上面返回的imageHeight = - LeadingSpace才合适。_forwardedContainerView.frame = CGRectMake(0, originY, UI_SCREEN_WIDTH, forwardContentHeight + imageHeight + LeadingSpace * 3);
TapImageView是UIImageView的子类,主要是添加了手势,实现点击图片展开大图的效果
TapImageView.h文件:
#import <UIKit/UIKit.h>@interface TapImageView : UIImageView@property (copy, nonatomic) void (^didTouchImage)(NSInteger index);- (instancetype)initWithFrame:(CGRect)frame;/** 设置图片地址 @param url 图片地址 @param index 图片下标 */- (void)setImageUrl:(NSString *)url index:(NSInteger)index;@end
TapImageView.m文件
#import "TapImageView.h"#import "UIImageView+WebCache.h"@interface TapImageView ()@property (assign, nonatomic) NSInteger index;@end@implementation TapImageView- (instancetype)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) { [self initView];}return self;}- (void)initView {//添加单击手势UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageViewTapAction:)];gesture.numberOfTapsRequired = 1;self.userInteractionEnabled = YES;[self addGestureRecognizer:gesture];}//发送点击图片的通知,并传回下标- (void)imageViewTapAction:(UITapGestureRecognizer *)gesture {if (_didTouchImage) { _didTouchImage(_index);}}/** 设置图片地址 @param url 图片地址 @param index 图片下标 */- (void)setImageUrl:(NSString *)url index:(NSInteger)index {if (url) { [self sd_setImageWithURL:[NSURL URLWithString:url]];}_index = index;}
在Cell中,会根据传回的点击图片下标展示相应图片的大图。
注意:
因为下文和配图等是运行时动态添加上去的,而cell是复用的,则每次使用cell的时候,需要将它们先移除。如果没有移除,则复用cell的时候就会发生cell位置错乱的情况。
- (void)removeView {//移除转发微博for (UIView *view in self.subviews) {if (view.tag == ForwardedContainerViewTag) {[view removeFromSuperview];break;}}//移除图片for (UIView *view in self.subviews) {if ([view isKindOfClass:[TapImageView class]]) {[view removeFromSuperview];}}}
在控制器中的实现
在控制器的xib中只有一个UITableView,可以直接在xib中指定UITableView的dataSource 和delegate,也可以在.m文件中再指定。
//注册cell,WeiboCellIdentifier是cell复用时用到的UINib *weiboNib = [UINib nibWithNibName:@"FitBoCell" bundle:nil];[_mainTableView registerNib:weiboNib forCellReuseIdentifier:WeiboCellIdentifier];//移除分割线_mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;_mainTableView.delegate = self;_mainTableView.dataSource = self;
接着实现UITableViewDataSource, UITableViewDelegate里面的方法。
//返回section的个数 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {return _weiboArray.count;}//返回每个section里面的行数- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {return 1;}//返回每个section底部的高度,默认为20PX, 就是如果不实现该方法或者return 0,实际都是返回20PX- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {return 0.001;}//返回每个section头部的高度,默认为20PX, 就是如果不实现该方法或者return 0,实际都是返回20PX- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {return 10;}//返回每一行的高度- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {NSInteger section = indexPath.section;WeiboModel *weibo = _weiboArray[section];return [FitBoCell getCellHeight:weibo];}//在这个方法里面设置cell的内容- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {NSInteger section = indexPath.section;//这个办法是模型转化,利用MJExtension框架 WeiboModel *weibo = [WeiboModel mj_objectWithKeyValues:_weiboArray[section]];FitBoCell *cell = [tableView dequeueReusableCellWithIdentifier:WeiboCellIdentifier];if (cell == nil) { cell = [[FitBoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:WeiboCellIdentifier];}//这里是点击非原创微博里面的原创微博的回调,也就是_forwardedContainerView的点击回调__weak typeof(self) weakSelf = self;cell.didTouchForwardedWeibo = ^(WeiboModel *weibo) { //跳转到微博的详情页面 [weakSelf forwardedWeiboTouch:weibo];};[cell setWeiboInfo:weibo];return cell;}//cell的点击响应事件,跳转到微博的详情页面- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {[tableView deselectRowAtIndexPath:indexPath animated:YES];NSInteger section = indexPath.section;WeiboModel *weibo = [WeiboModel mj_objectWithKeyValues:_weiboArray[section]];CommentOrRepostListViewController *listVC = [CommentOrRepostListViewController new];[listVC setWeibo:weibo offset:NO];[self.navigationController pushViewController:listVC animated:YES];}
其中,因为cell的高度是根据实际情况不定的,所以使用了类方法来获取。[FitBoCell getCellHeight:weibo]
/** * 获取cell的高度 * * @param weibo weibo * * @return height */+ (CGFloat)getCellHeight:(WeiboModel *)weibo {CGFloat contentHeight = [FitBoUI getLabelHeightWithText:weibo.text width:UI_SCREEN_WIDTH - LeadingSpace * 2 font:FontSize12];CGFloat originY = ContentLabelOriginY + contentHeight + LeadingSpace;if (weibo.retweeted_status == nil) { //原创微博 CGFloat imageHeight = [self getImageHeight:weibo.pic_urls.count]; return originY + imageHeight + LeadingSpace + ButtonHeight;}else { //非原创微博 WeiboModel *forwardWeibo = weibo.retweeted_status; NSString *forwardText = [NSString stringWithFormat:@"@%@:%@", forwardWeibo.user.name, forwardWeibo.text]; CGFloat imageHeight = [self getImageHeight:forwardWeibo.pic_urls.count]; CGFloat forwardContentHeight = [FitBoUI getLabelHeightWithText:forwardText width:UI_SCREEN_WIDTH - LeadingSpace * 2 font:FontSize12]; return originY + LeadingSpace + forwardContentHeight + imageHeight + LeadingSpace * 2 + ButtonHeight;}}//获取图片的整体高度+ (CGFloat)getImageHeight:(NSInteger)count {if (count < 1) { //上面计算高度的时候预留了配图上下两个间隙的高度。所以,如果没有配图,返回 - LeadingSpace才合适。 return - LeadingSpace;}else if (count == 1) { return UI_SCREEN_WIDTH * 0.55;}else if (count / 3 < 1 || count == 3) { //一行 return (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * 2) / 3;}else if (count > 3 && count <= 6) { //两行 return (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * 2) / 3 * 2 + ImageSpace;}else { //三行 return (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * 2) + ImageSpace * 2;}}
其他的点击事件的响应方法等,就不累赘了。最后再放一张非原创微博的效果图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。
新闻热点
疑难解答