前言

做一个微博客户端的第三方是自学的第一个实践的项目,自从从事iOS工作之后,就把这个项目给搁置了。趁现在过年回来有些空闲时间,再次修改(总觉得项目就是不停地修改)。并且记录一点东西,以后可再回头看看从前走过的路,挖过的坑。这是一条微博的展示,不是整个项目。

废话不多说,先上效果图:

拆分控件

在开始动手写代码之前,我们得先确定怎么去实现这样子的布局,也就是分析需要用到哪些控件。

  • 观察微博客户端,整体是可滑动的,而且界面展示比较规律的,所以应该是使用UITableView实现的。那么一条微博应该是用UITableViewCell 来实现的,这个由点击时,整条微博都变色可以肯定。
  • 一条微博与其他的微博之间是有大约10PX的间距,可以认为每个Section就只有一个Cell。

  • 每条微博的共同部分包括:头像,用户名称,发布时间与发布来源,微博正文,底部的转发,评论,赞。不同的部分有:配图,非原创微博的正文。(视频,文章等在这个项目中不做考虑)所以共同部分可以直接在xib上固定,不同部分则需要在.m文件用代码来写。

  • 控件的确定:头像和配图使用UIImageView,用户名称,发布时间与来源,微博正文,非原创微博的正文都是使用UILabel,而底部的转发,评论,赞使用UIButton。

  • 当一条微博是非原创微博(转发微博),根据点击被转发的微博的变色情况,可以确定转发微博是一个整体,可以确定转发微博是放在一个UIView上再添加到Cell上面的。

布局

放上一张xib的布局图:(button是与底部进行约束的)

共同的部分,先设置一些参数。
- (void)awakeFromNib {
[super awakeFromNib];
_contentLabel.numberOfLines = ;//正文多行 //圆形头像
_headImageView.layer.masksToBounds = YES;
_headImageView.layer.cornerRadius = HeadImageHeight / ; //设置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 == ) {
    //一列
    column = ;
    imageWidth = UI_SCREEN_WIDTH * 0.55;
    }
    else if (_imageArray.count == || _imageArray.count == ) {
    //两列
    column = ;
    imageWidth = (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * ) / ;
    }
    else {
    //三列
    column = ;
    imageWidth = (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * ) / ;
    } //根据图片的数量和列数获得行数
    if (_imageArray.count % column == ) {
    row = _imageArray.count / column;
    }
    else {
    row = _imageArray.count / column + ;
    }

确定了配图的大小,再根据位置,就可以创建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坐标。

//根据位置创建imageView
for (int i = ; i < row; i++) {
for (int j = ; 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 *  font:FontSize12];
CGFloat originY = ContentLabelOriginY + contentHeight;
originY += LeadingSpace;
_forwardedContainerView = [[UIView alloc] initWithFrame:CGRectMake(, originY, UI_SCREEN_WIDTH, )];
_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 * font:FontSize12];
UILabel *forwardedContentLabel = [[UILabel alloc] initWithFrame:CGRectMake(LeadingSpace, LeadingSpace, UI_SCREEN_WIDTH - LeadingSpace * , forwardContentHeight)];
forwardedContentLabel.font = FontSize12;
forwardedContentLabel.numberOfLines = ;
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(, originY, UI_SCREEN_WIDTH, forwardContentHeight + imageHeight + LeadingSpace * );

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 = ;
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 ;
}
//返回每个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 ;
}
//返回每一行的高度
- (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 * 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 * font:FontSize12]; return originY + LeadingSpace + forwardContentHeight + imageHeight + LeadingSpace * + ButtonHeight;
}
} //获取图片的整体高度
+ (CGFloat)getImageHeight:(NSInteger)count {
if (count < ) {
//上面计算高度的时候预留了配图上下两个间隙的高度。所以,如果没有配图,返回 - LeadingSpace才合适。
return - LeadingSpace;
}
else if (count == ) {
return UI_SCREEN_WIDTH * 0.55;
}
else if (count / < || count == ) {
//一行
return (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * ) / ;
}
else if (count > && count <= ) {
//两行
return (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * ) / * + ImageSpace;
}
else {
//三行
return (UI_SCREEN_WIDTH - (LeadingSpace + ImageSpace) * ) + ImageSpace * ;
}
}

其他的点击事件的响应方法等,就不累赘了。最后再放一张非原创微博的效果图:

 
 

iOS仿微博客户端一条微博的布局的更多相关文章

  1. iOS开发实践:一个类微博客户端从启动到与用户交互的过程

    本文基于数据字典和数据流图两种工具讲述一个完整微博客户端的实现.数据字典和数据流图都可以用来表达线程的执行流程,同时定义了需要的类,是进一步设计类的基础. 数据字典实际上是一张表,表的第一个字段是程序 ...

  2. 【iOS微博客户端开发】1、微博整体项目的构建

    回顾自己做过的项目,总结里面的知识点,分享自己参照WXHL的视频开发的一个模拟微博客户端的过程,为了还在IOS上找不到项目参考的朋友,这里会由一系列手把手的教程,如有不足,还希望可以抖抖小手,献上您宝 ...

  3. [iOS微博项目 - 4.5] - 每条微博的底部工具条

    github: https://github.com/hellovoidworld/HVWWeibo A.每条微博的底部工具条 1.需求 每条微博底部都有一个工具条 显示3个按钮:评论.转发.赞 按钮 ...

  4. [iOS微博项目 - 4.3] - 设置每条微博边框样式

    github: https://github.com/hellovoidworld/HVWWeibo A.设置每条微博边框样式 1.需求 不需要分割线 每个微博之间留有一定的间隙   2.思路 直接设 ...

  5. Android应用内使用新浪微博SDK发送微博(不调用微博客户端)

    需求 手头的一个应用需要添加分享到新浪微博的功能,这个功能在现在的应用上是非常的普遍的了. 分享到新浪微博,其实就是发送一条特定内容的微博,所以需要用到新浪微博SDK了. 微博SDK SDK的下载地址 ...

  6. iOS开发之多表视图滑动切换示例(仿"头条"客户端)---优化篇

    前几天发布了一篇iOS开发之多表视图滑动切换示例(仿"头条"客户端)的博客,之所以写这篇博客,是因为一位iOS初学者提了一个问题,简单的写了个demo做了个示范,让其在基础上做扩展 ...

  7. 64位ubuntu下安装微博客户端的方法

    最近安装了12.04的ubuntu系统,在unbutu提供的软件中心找不到微博客户端的应用,但在新浪的http://sinatair.sinaapp.com/下找到了官方的客户端. 于是下载了linu ...

  8. android oauth 微博客户端 架构一

    最近研究oauth协议,为了进一步 的巩固自己的学习成果,顾完成了android的新浪客户端.他的架构如下: UI层微博中的各个窗体  就是所谓的各个activitylogic层程序的核心控制调度模块 ...

  9. 分享一个难得的YiBo微博客户端应用源码Android版

    今天给大家分享一款,YiBo微博客户端应用源码,这是一款专为Android用户打造的聚合型微博客户端,完美支持新浪微博.腾讯微博.搜狐微博.网易微博和饭否五个微博平台,界面清爽,使用简单轻巧,支持多账 ...

随机推荐

  1. JAVA 遍历文件夹下的所有文件(递归调用和非递归调用)

    JAVA 遍历文件夹下的所有文件(递归调用和非递归调用) 1.不使用递归的方法调用. public void traverseFolder1(String path) { int fileNum = ...

  2. Selenium2(java)框架设计 九

    设计框架原则: 数据分离,业务层和逻辑层不要混杂在一起. 设计图: 框架结构初始化: com.wymall.test:这是存放这个框架源代码的根目录 base:里面有个基类(BaseParpaare. ...

  3. IndexAction.java (Java之负基础实战)

    生成Get and Set 方法: 例如:public String view; 右击view > Source > Generate Getters and Setters...

  4. Java语言Socket接口用法详解

    Socket接口用法详解   在Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Serve ...

  5. 国民身份证号码校验之“C#/Winform方法实现+案例分析”

    根据[中华人民共和国国家标准 GB 11643-1999]中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成.排列顺序从左至右依次为:六位数字地址码,八位数字出 ...

  6. Win7 x64 Eclipse无法识别手机 / adb interface有黄色感叹号,无法识别

    今天公司停电,因此把安卓项目带回宿舍做.宿舍的笔记本,装的是Win7 x64,手机连上电脑后,windows可以识别,但Eclipse的DDMS中却无法识别,什么都没有: 然后打开设备管理器查看,发现 ...

  7. HDU1548:A strange lift(Dijkstra或BFS)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1548 题意:电梯每层有一个数,例如第n层有个数k, 那么这一层只能上k层或下k层,但是不能低于一层或高 ...

  8. jQuery原型技术分解

    jQuery原型技术分解 起源----原型继承 用户过javascript的都会明白,在javascript脚本中到处都是 函数,函数可以归置代码段,把相对独立的功能封闭在一个函数包中.函数也可以实现 ...

  9. js模块化开发——AMD规范

    这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就 ...

  10. 网格视图(GridView)功能和用法

    GridView用于在界面上按行.列分布的方式来显示多个组件.GridView和ListView有共同的父类:AbsListView,因此GridView和ListView具有很高的相似性,它们都是列 ...