仿微博客戶端一條微博的布局

前言

做一個微博客戶端的第三方是自學的第一個實踐的項目辜贵,自從從事iOS工作之后毙沾,就把這個項目給擱置了。趁現(xiàn)在過年回來有些空閑時間骇陈,再次修改(總覺得項目就是不停地修改)震庭。并且記錄一點東西,以后可再回頭看看從前走過的路你雌,挖過的坑器联。這是一條微博的展示二汛,不是整個項目。

廢話不多說拨拓,先上效果圖:


仿微博布局.png

拆分控件

在開始動手寫代碼之前肴颊,我們得先確定怎么去實現(xiàn)這樣子的布局,也就是分析需要用到哪些控件渣磷。

  • 觀察微博客戶端婿着,整體是可滑動的,而且界面展示比較規(guī)律的醋界,所以應該是使用UITableView實現(xiàn)的竟宋。那么一條微博應該是用UITableViewCell 來實現(xiàn)的,這個由點擊時形纺,整條微博都變色可以肯定丘侠。

  • 一條微博與其他的微博之間是有大約10PX的間距,可以認為每個Section就只有一個Cell逐样。

  • 每條微博的共同部分包括:頭像蜗字,用戶名稱,發(fā)布時間與發(fā)布來源脂新,微博正文挪捕,底部的轉(zhuǎn)發(fā),評論戏羽,贊担神。不同的部分有:配圖,非原創(chuàng)微博的正文始花。(視頻,文章等在這個項目中不做考慮)所以共同部分可以直接在xib上固定孩锡,不同部分則需要在.m文件用代碼來寫酷宵。

  • 控件的確定:頭像和配圖使用UIImageView,用戶名稱躬窜,發(fā)布時間與來源浇垦,微博正文,非原創(chuàng)微博的正文都是使用UILabel荣挨,而底部的轉(zhuǎn)發(fā)男韧,評論,贊使用UIButton默垄。

  • 當一條微博是非原創(chuàng)微博(轉(zhuǎn)發(fā)微博)此虑,根據(jù)點擊被轉(zhuǎn)發(fā)的微博的變色情況,可以確定轉(zhuǎn)發(fā)微博是一個整體口锭,可以確定轉(zhuǎn)發(fā)微博是放在一個UIView上再添加到Cell上面的朦前。

布局

放上一張xib的布局圖:(button是與底部進行約束的)


xib.png

共同的部分介杆,先設置一些參數(shù)。

- (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張。首先先根據(jù)配圖的張數(shù)和屏幕的寬度確定圖片的大小imageWidth恩伺,然后再確定行數(shù)和列數(shù)赴背。

  1. 只有一張配圖時,imageWidth = 屏幕寬度 * 0.55;

  2. 配圖超過一張時晶渠,imageWidth = (屏幕寬度 - 間隙) / 3;

  3. 配圖是2張或者4張時癞尚,分為兩列布局,而配圖3張或者大于4張時乱陡,則分為三列布局浇揩。
    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养盗。 配圖的位置則在正文起始位置 + 正文的高度 + 間隙,而獲取正文的高度由以下方法來完成:

 *  計算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;
}

對于原創(chuàng)微博正文的起始位置可以由xib看出來适篙,頭像的高度固定為48往核,而上下的間隙為8, 則起始位置Y坐標為 48 + 16 = 64;而對于非原創(chuàng)微博嚷节,正文的起始位置Y坐標為8 (此處的8是相對于配圖的父容器UIView的位置聂儒,對于非原創(chuàng)微博而言,更重要的是計算出父容器UIView在Cell中的位置);
然后根據(jù)配圖的位置和大小創(chuàng)建UIImageView硫痰,如下圖衩婚,其中originY為第一張配圖的起始位置的Y坐標。

//根據(jù)位置創(chuàng)建imageView
for (int i = 0; i < row; i++) {
    for (int j = 0; j < column; j++) {
        //用來判斷數(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通知效斑,點擊了圖片非春,展示大圖
                __weak typeof(self) weakSelf = self;
                imageView.didTouchImage = ^(NSInteger index) {
                    [weakSelf showFullScreenImage:index];
                };
                
                [imageView setImageUrl:imageUrl index:i * column + j];

                //原創(chuàng)微博直接添加的cell中,非原創(chuàng)則加入一個容器中UIView缓屠,再將容器加入cell中
                if (isForward) {
                    [_forwardedContainerView addSubview:imageView];
                }
                else {
                    [self addSubview:imageView];
                }
            }
        }
        else {
            //越界后跳出for循環(huán)
            break;
        }
    }
}

TapImageView是UIImageView的子類奇昙,主要是添加了手勢,實現(xiàn)點擊圖片展開大圖的效果敌完,后面再做詳細介紹储耐。

非原創(chuàng)微博有兩個正文,分別用“上文”和“下文”來區(qū)分吧蠢挡。上文已經(jīng)在xib中弧岳,而下文和配圖是放在_forwardedContainerView(UIView)中凳忙,然后再添加到Cell中的,所以要計算它的起始位置Y坐標禽炬。上文的Y坐標已經(jīng)確定為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];

//添加單擊手勢柳恐,點擊原創(chuàng)微博,進入該微博的詳情頁面
[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];

//創(chuàng)建imageview,并根據(jù)修改實際高度绎巨,pic_urls是圖片的網(wǎng)址數(shù)組近尚。得到的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.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];
}

//發(fā)送點擊圖片的通知,并傳回下標
- (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中留瞳,會根據(jù)傳回的點擊圖片下標展示相應圖片的大圖拒迅。

注意:

因為下文和配圖等是運行時動態(tài)添加上去的,而cell是復用的她倘,則每次使用cell的時候璧微,需要將它們先移除。如果沒有移除帝牡,則復用cell的時候就會發(fā)生cell位置錯亂的情況往毡。

- (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];
    }
}
}

在控制器中的實現(xiàn)

在控制器的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;

接著實現(xiàn)UITableViewDataSource, UITableViewDelegate里面的方法罩息。

//返回section的個數(shù)
  - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return _weiboArray.count;
}

//返回每個section里面的行數(shù)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
//返回每個section底部的高度,默認為20PX, 就是如果不實現(xiàn)該方法或者return 0个扰,實際都是返回20PX
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.001;
}
//返回每個section頭部的高度瓷炮,默認為20PX, 就是如果不實現(xiàn)該方法或者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的內(nèi)容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = indexPath.section;
//這個辦法是模型轉(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];
}

//這里是點擊非原創(chuàng)微博里面的原創(chuàng)微博的回調(diào)娘香,也就是_forwardedContainerView的點擊回調(diào)
__weak typeof(self) weakSelf = self;
cell.didTouchForwardedWeibo = ^(WeiboModel *weibo) {
    //跳轉(zhuǎn)到微博的詳情頁面
    [weakSelf forwardedWeiboTouch:weibo];
};

[cell setWeiboInfo:weibo];

return cell;
}

//cell的點擊響應事件苍狰,跳轉(zhuǎn)到微博的詳情頁面
- (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的高度是根據(jù)實際情況不定的烘绽,所以使用了類方法來獲取淋昭。[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) {
   //上面計算高度的時候預留了配圖上下兩個間隙的高度。所以安接,如果沒有配圖翔忽,返回 - 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;
}
}

其他的點擊事件的響應方法等盏檐,就不累贅了歇式。最后再放一張非原創(chuàng)微博的效果圖:

非原創(chuàng)微博.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胡野,隨后出現(xiàn)的幾起案子材失,更是在濱河造成了極大的恐慌,老刑警劉巖硫豆,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龙巨,死亡現(xiàn)場離奇詭異,居然都是意外死亡够庙,警方通過查閱死者的電腦和手機恭应,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耘眨,“玉大人昼榛,你說我怎么就攤上這事√弈眩” “怎么了胆屿?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長偶宫。 經(jīng)常有香客問我非迹,道長,這世上最難降的妖魔是什么纯趋? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任憎兽,我火速辦了婚禮,結(jié)果婚禮上吵冒,老公的妹妹穿的比我還像新娘纯命。我一直安慰自己,他們只是感情好痹栖,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布亿汞。 她就那樣靜靜地躺著,像睡著了一般揪阿。 火紅的嫁衣襯著肌膚如雪疗我。 梳的紋絲不亂的頭發(fā)上咆畏,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音吴裤,去河邊找鬼旧找。 笑死,一個胖子當著我的面吹牛嚼摩,可吹牛的內(nèi)容都是我干的钦讳。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼枕面,長吁一口氣:“原來是場噩夢啊……” “哼愿卒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起潮秘,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤琼开,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枕荞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柜候,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年躏精,在試婚紗的時候發(fā)現(xiàn)自己被綠了渣刷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡矗烛,死狀恐怖辅柴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞭吃,我是刑警寧澤碌嘀,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站歪架,受9級特大地震影響股冗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜和蚪,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一止状、第九天 我趴在偏房一處隱蔽的房頂上張望齐板。 院中可真熱鬧比然,春花似錦嗽冒、人聲如沸絮宁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春泣崩,著一層夾襖步出監(jiān)牢的瞬間少梁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工矫付, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凯沪,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓买优,卻偏偏與公主長得像妨马,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子杀赢,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容