iOS仿微博客戶端一條微博的展示效果
前言
做一個(gè)微博客戶端的第三方是自學(xué)的第一個(gè)實(shí)踐的項(xiàng)目,自從從事iOS工作之后,就把這個(gè)項(xiàng)目給擱置了。趁現(xiàn)在過(guò)年回來(lái)有些空閑時(shí)間,再次修改(總覺得項(xiàng)目就是不停地修改)。并且記錄一點(diǎn)東西,以后可再回頭看看從前走過(guò)的路,挖過(guò)的坑。這是一條微博的展示,不是整個(gè)項(xiàng)目。
廢話不多說(shuō),先上效果圖:

拆分控件
在開始動(dòng)手寫代碼之前,我們得先確定怎么去實(shí)現(xiàn)這樣子的布局,也就是分析需要用到哪些控件。
觀察微博客戶端,整體是可滑動(dòng)的,而且界面展示比較規(guī)律的,所以應(yīng)該是使用UITableView實(shí)現(xiàn)的。那么一條微博應(yīng)該是用UITableViewCell 來(lái)實(shí)現(xiàn)的,這個(gè)由點(diǎn)擊時(shí),整條微博都變色可以肯定。
一條微博與其他的微博之間是有大約10PX的間距,可以認(rèn)為每個(gè)Section就只有一個(gè)Cell。
每條微博的共同部分包括:頭像,用戶名稱,發(fā)布時(shí)間與發(fā)布來(lái)源,微博正文,底部的轉(zhuǎn)發(fā),評(píng)論,贊。不同的部分有:配圖,非原創(chuàng)微博的正文。(視頻,文章等在這個(gè)項(xiàng)目中不做考慮)所以共同部分可以直接在xib上固定,不同部分則需要在.m文件用代碼來(lái)寫。
控件的確定:頭像和配圖使用UIImageView,用戶名稱,發(fā)布時(shí)間與來(lái)源,微博正文,非原創(chuàng)微博的正文都是使用UILabel,而底部的轉(zhuǎn)發(fā),評(píng)論,贊使用UIButton。
當(dāng)一條微博是非原創(chuàng)微博(轉(zhuǎn)發(fā)微博),根據(jù)點(diǎn)擊被轉(zhuǎn)發(fā)的微博的變色情況,可以確定轉(zhuǎn)發(fā)微博是一個(gè)整體,可以確定轉(zhuǎn)發(fā)微博是放在一個(gè)UIView上再添加到Cell上面的。
布局
放上一張xib的布局圖:(button是與底部進(jìn)行約束的)

共同的部分,先設(shè)置一些參數(shù)。
- (void)awakeFromNib {
[super awakeFromNib];
_contentLabel.numberOfLines = 0;//正文多行
//圓形頭像
_headImageView.layer.masksToBounds = YES;
_headImageView.layer.cornerRadius = HeadImageHeight / 2;
//設(shè)置tag,為了后面識(shí)別點(diǎn)擊哪個(gè)按鈕
_repostButton.tag = RepostButtonTag;
_commentButton.tag = CommentButtonTag;
_likeButton.tag = LikeButtonTag;
}
先說(shuō)配圖,微博的配圖最多9張。首先先根據(jù)配圖的張數(shù)和屏幕的寬度確定圖片的大小imageWidth,然后再確定行數(shù)和列數(shù)。
1、只有一張配圖時(shí),imageWidth = 屏幕寬度 * 0.55;
2、配圖超過(guò)一張時(shí),imageWidth = (屏幕寬度 - 間隙) / 3;
3、配圖是2張或者4張時(shí),分為兩列布局,而配圖3張或者大于4張時(shí),則分為三列布局。
LeadingSpace 是圖片與兩側(cè)屏幕的間隙,為8PX, ImageSpace是圖片之間的間隙為4PX。UI_SCREEN_WIDTH是屏幕寬度。
//根據(jù)圖片數(shù)量獲得列數(shù)
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;
}
//根據(jù)圖片的數(shù)量和列數(shù)獲得行數(shù)
if (_imageArray.count % column == 0) {
row = _imageArray.count / column;
}
else {
row = _imageArray.count / column + 1;
}
確定了配圖的大小,再根據(jù)位置,就可以創(chuàng)建UIImageView。 配圖的位置則在正文起始位置 + 正文的高度 + 間隙,而獲取正文的高度由以下方法來(lái)完成:
* 計(jì)算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);//設(shè)置一個(gè)行高的上限
CGSize returnSize;
NSDictionary *attribute = @{ NSFontAttributeName : font };
returnSize = [text boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:attribute
context:nil].size;
return returnSize.height;
}
對(duì)于原創(chuàng)微博正文的起始位置可以由xib看出來(lái),頭像的高度固定為48,而上下的間隙為8, 則起始位置Y坐標(biāo)為 48 + 16 = 64;而對(duì)于非原創(chuàng)微博,正文的起始位置Y坐標(biāo)為8 (此處的8是相對(duì)于配圖的父容器UIView的位置,對(duì)于非原創(chuàng)微博而言,更重要的是計(jì)算出父容器UIView在Cell中的位置);
然后根據(jù)配圖的位置和大小創(chuàng)建UIImageView,如下圖,其中originY為第一張配圖的起始位置的Y坐標(biāo)。
//根據(jù)位置創(chuàng)建imageView
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
//用來(lái)判斷數(shù)據(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通知,點(diǎn)擊了圖片,展示大圖
__weak typeof(self) weakSelf = self;
imageView.didTouchImage = ^(NSInteger index) {
[weakSelf showFullScreenImage:index];
};
[imageView setImageUrl:imageUrl index:i * column + j];
//原創(chuàng)微博直接添加的cell中,非原創(chuàng)則加入一個(gè)容器中UIView,再將容器加入cell中
if (isForward) {
[_forwardedContainerView addSubview:imageView];
}
else {
[self addSubview:imageView];
}
}
}
else {
//越界后跳出for循環(huán)
break;
}
}
}
TapImageView是UIImageView的子類,主要是添加了手勢(shì),實(shí)現(xiàn)點(diǎn)擊圖片展開大圖的效果,后面再做詳細(xì)介紹。
非原創(chuàng)微博有兩個(gè)正文,分別用“上文”和“下文”來(lái)區(qū)分吧。上文已經(jīng)在xib中,而下文和配圖是放在_forwardedContainerView(UIView)中,然后再添加到Cell中的,所以要計(jì)算它的起始位置Y坐標(biāo)。上文的Y坐標(biāo)已經(jīng)確定為64了,而_forwardedContainerView與上文之間的間隙為8,所以下文的Y坐標(biāo) = 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]; //添加單擊手勢(shì),點(diǎn)擊原創(chuàng)微博,進(jìn)入該微博的詳情頁(yè)面 [self forwardedContainerViewAddGesture]; [self addSubview:_forwardedContainerView];
_forwardedContainerView的高度是隨便給的,需要在計(jì)算實(shí)際高度之后再重新賦值。
//下文是用戶名稱和文字拼湊而來(lái)。 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]; //創(chuàng)建imageview,并根據(jù)修改實(shí)際高度,pic_urls是圖片的網(wǎng)址數(shù)組。得到的imageHeight為所有圖片以及圖片之間的間隙總和。 CGFloat imageHeight = [self initImageView:forwardWeibo.pic_urls originY:forwardContentHeight + LeadingSpace isForward:YES]; //此處無(wú)論有沒有配圖,都預(yù)留了配圖上下兩個(gè)間隙的高度。所以,如果沒有配圖,上面返回的imageHeight = - LeadingSpace才合適。 _forwardedContainerView.frame = CGRectMake(0, originY, UI_SCREEN_WIDTH, forwardContentHeight + imageHeight + LeadingSpace * 3);
TapImageView是UIImageView的子類,主要是添加了手勢(shì),實(shí)現(xiàn)點(diǎn)擊圖片展開大圖的效果
TapImageView.h文件:
#import <UIKit/UIKit.h> @interface TapImageView : UIImageView @property (copy, nonatomic) void (^didTouchImage)(NSInteger index); - (instancetype)initWithFrame:(CGRect)frame; /** 設(shè)置圖片地址 @param url 圖片地址 @param index 圖片下標(biāo) */ - (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 {
//添加單擊手勢(shì)
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageViewTapAction:)];
gesture.numberOfTapsRequired = 1;
self.userInteractionEnabled = YES;
[self addGestureRecognizer:gesture];
}
//發(fā)送點(diǎn)擊圖片的通知,并傳回下標(biāo)
- (void)imageViewTapAction:(UITapGestureRecognizer *)gesture {
if (_didTouchImage) {
_didTouchImage(_index);
}
}
/**
設(shè)置圖片地址
@param url 圖片地址
@param index 圖片下標(biāo)
*/
- (void)setImageUrl:(NSString *)url index:(NSInteger)index {
if (url) {
[self sd_setImageWithURL:[NSURL URLWithString:url]];
}
_index = index;
}
在Cell中,會(huì)根據(jù)傳回的點(diǎn)擊圖片下標(biāo)展示相應(yīng)圖片的大圖。
注意:
因?yàn)橄挛暮团鋱D等是運(yùn)行時(shí)動(dòng)態(tài)添加上去的,而cell是復(fù)用的,則每次使用cell的時(shí)候,需要將它們先移除。如果沒有移除,則復(fù)用cell的時(shí)候就會(huì)發(fā)生cell位置錯(cuò)亂的情況。
- (void)removeView {
//移除轉(zhuǎn)發(fā)微博
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];
}
}
}
在控制器中的實(shí)現(xiàn)
在控制器的xib中只有一個(gè)UITableView,可以直接在xib中指定UITableView的dataSource 和delegate,也可以在.m文件中再指定。
//注冊(cè)cell,WeiboCellIdentifier是cell復(fù)用時(shí)用到的 UINib *weiboNib = [UINib nibWithNibName:@"FitBoCell" bundle:nil]; [_mainTableView registerNib:weiboNib forCellReuseIdentifier:WeiboCellIdentifier]; //移除分割線 _mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone; _mainTableView.delegate = self; _mainTableView.dataSource = self;
接著實(shí)現(xiàn)UITableViewDataSource, UITableViewDelegate里面的方法。
//返回section的個(gè)數(shù)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return _weiboArray.count;
}
//返回每個(gè)section里面的行數(shù)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
//返回每個(gè)section底部的高度,默認(rèn)為20PX, 就是如果不實(shí)現(xiàn)該方法或者return 0,實(shí)際都是返回20PX
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.001;
}
//返回每個(gè)section頭部的高度,默認(rèn)為20PX, 就是如果不實(shí)現(xiàn)該方法或者return 0,實(shí)際都是返回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];
}
//在這個(gè)方法里面設(shè)置cell的內(nèi)容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = indexPath.section;
//這個(gè)辦法是模型轉(zhuǎn)化,利用MJExtension框架
WeiboModel *weibo = [WeiboModel mj_objectWithKeyValues:_weiboArray[section]];
FitBoCell *cell = [tableView dequeueReusableCellWithIdentifier:WeiboCellIdentifier];
if (cell == nil) {
cell = [[FitBoCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:WeiboCellIdentifier];
}
//這里是點(diǎn)擊非原創(chuàng)微博里面的原創(chuàng)微博的回調(diào),也就是_forwardedContainerView的點(diǎn)擊回調(diào)
__weak typeof(self) weakSelf = self;
cell.didTouchForwardedWeibo = ^(WeiboModel *weibo) {
//跳轉(zhuǎn)到微博的詳情頁(yè)面
[weakSelf forwardedWeiboTouch:weibo];
};
[cell setWeiboInfo:weibo];
return cell;
}
//cell的點(diǎn)擊響應(yīng)事件,跳轉(zhuǎn)到微博的詳情頁(yè)面
- (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];
}
其中,因?yàn)閏ell的高度是根據(jù)實(shí)際情況不定的,所以使用了類方法來(lái)獲取。[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) {
//原創(chuàng)微博
CGFloat imageHeight = [self getImageHeight:weibo.pic_urls.count];
return originY + imageHeight + LeadingSpace + ButtonHeight;
}
else {
//非原創(chuàng)微博
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) {
//上面計(jì)算高度的時(shí)候預(yù)留了配圖上下兩個(gè)間隙的高度。所以,如果沒有配圖,返回 - 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;
}
}
其他的點(diǎn)擊事件的響應(yīng)方法等,就不累贅了。最后再放一張非原創(chuàng)微博的效果圖:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS開發(fā)之隨機(jī)生成兩圓之間的標(biāo)準(zhǔn)圓
這篇文章主要給大家介紹了iOS如何實(shí)現(xiàn)在兩圓之間隨機(jī)生成標(biāo)準(zhǔn)圓的方法,實(shí)現(xiàn)的效果類似尋找附近人或者附近商家的動(dòng)態(tài)效果,有需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-01-01
Observing?KVO?Key-Value基本使用原理示例詳解
這篇文章主要為大家介紹了Observing?KVO?Key-Value基本使用原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
IOS 開發(fā)之 UITextField限制字?jǐn)?shù)的方法
這篇文章主要介紹了IOS 開發(fā)之 UITextField限制字?jǐn)?shù)的方法的相關(guān)資料,這里提供實(shí)現(xiàn)限制最大字?jǐn)?shù)的方法,需要的朋友可以參考下2017-08-08
iOS10 適配遠(yuǎn)程推送功能實(shí)現(xiàn)代碼
這篇文章主要介紹了iOS10 適配遠(yuǎn)程推送功能實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
詳解iOS - ASIHTTPRequest 網(wǎng)絡(luò)請(qǐng)求
本篇文章主要介紹了iOS - ASIHTTPRequest 網(wǎng)絡(luò)請(qǐng)求 ,詳細(xì)的介紹了 ASIHTTPRequest的使用,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12

