iOS仿微博客户端一条微博的展示效果

时间:2022-06-08 14:54:10

前言

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

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

iOS仿微博客户端一条微博的展示效果

拆分控件

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

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

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

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

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

布局

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

iOS仿微博客户端一条微博的展示效果

共同的部分,先设置一些参数。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
- (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是屏幕宽度。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//根据图片数量获得列数
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。 配图的位置则在正文起始位置 + 正文的高度 + 间隙,而获取正文的高度由以下方法来完成:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
* 计算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坐标。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//根据位置创建imageview
for (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

?
1
2
3
4
5
6
7
8
9
10
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的高度是随便给的,需要在计算实际高度之后再重新赋值。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//下文是用户名称和文字拼凑而来。
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文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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文件

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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位置错乱的情况。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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文件中再指定。

?
1
2
3
4
5
6
7
8
//注册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里面的方法。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//返回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]

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * 获取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;
}
}

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

iOS仿微博客户端一条微博的展示效果

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。