首页显示微博列表,是微博的核心部分,这一章节,我们主要是显示出微博的列表。
导入第三方类库
pod ‘SDWebImage‘, ‘~> 3.7.3‘ pod ‘MJRefresh‘, ‘~> 2.4.12‘ pod ‘MJExtension‘, ‘~> 2.5.14‘
需求分析
由于Cell的高度是不一样的,因而采用自定义cell的方式来实现。具体实现思路,请参数之前的文章:
iOS UI基础-9.2 UITableView 简单微博列表
代码实现
1、根据新浪微博的API文档,需要定义两个模型(User/Status),由于我们还需要计算控制的位置,另外定义一个StatusFrame模型。
User.h
// // User.h // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import <Foundation/Foundation.h> typedef enum { UserVerifiedTypeNone = -1, // 没有任何认证 UserVerifiedPersonal = 0, // 个人认证 UserVerifiedOrgEnterprice = 2, // 企业官方:CSDN、EOE、搜狐新闻客户端 UserVerifiedOrgMedia = 3, // 媒体官方:程序员杂志、苹果汇 UserVerifiedOrgWebsite = 5, // 网站官方:猫扑 UserVerifiedDaren = 220 // 微博达人 } UserVerifiedType; @interface User : NSObject /** string 字符串型的用户UID*/ @property (nonatomic, copy) NSString *idstr; /** string 友好显示名称*/ @property (nonatomic, copy) NSString *name; /** string 用户头像地址,50×50像素*/ @property (nonatomic, copy) NSString *profile_image_url; /** 会员类型 > 2代表是会员 */ @property (nonatomic, assign) int mbtype; /** 会员等级 */ @property (nonatomic, assign) int mbrank; @property (nonatomic, assign, getter = isVip) BOOL vip; /** 认证类型 */ @property (nonatomic, assign) UserVerifiedType verified_type; @end
User.m
// // User.m // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import "User.h" @implementation User - (void)setMbtype:(int)mbtype { _mbtype = mbtype; self.vip = mbtype > 2; } @end
Status.h
// // Status.h // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import <Foundation/Foundation.h> @class User; @interface Status : NSObject /** string 字符串型的微博ID*/ @property (nonatomic, copy) NSString *idstr; /** string 微博信息内容*/ @property (nonatomic, copy) NSString *text; /** object 微博作者的用户信息字段 详细*/ @property (nonatomic, strong) User *user; /** string 微博创建时间*/ @property (nonatomic, copy) NSString *created_at; /** string 微博来源*/ @property (nonatomic, copy) NSString *source; /** 微博配图地址。多图时返回多图链接。无配图返回“[]” */ @property (nonatomic, strong) NSArray *pic_urls; @end
Status.m
#import "Status.h" @implementation Status // source == <a href="http://app.weibo.com/t/feed/2llosp" rel="nofollow">OPPO_N1mini</a> - (void)setSource:(NSString *)source { if (source.length != 0) { NSRange range; range.location = [source rangeOfString:@">"].location + 1; range.length = [source rangeOfString:@"</"].location - range.location; _source = [NSString stringWithFormat:@"来自%@", [source substringWithRange:range]]; } else{ _source = @"来自微博"; } } @end
StatusFrame.h
// // StatusFrame.h // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import <Foundation/Foundation.h> @class Status; // cell的边框宽度 #define StatusCellBorderW 10 // 昵称字体 #define StatusCellNameFont [UIFont systemFontOfSize:15] // 时间字体 #define StatusCellTimeFont [UIFont systemFontOfSize:12] // 来源字体 #define StatusCellSourceFont StatusCellTimeFont // 正文字体 #define StatusCellContentFont [UIFont systemFontOfSize:14] // 被转发微博的正文字体 #define StatusCellRetweetContentFont [UIFont systemFontOfSize:13] // cell之间的间距 #define StatusCellMargin 15 @interface StatusFrame : NSObject /** 存在微博模型 */ @property (nonatomic, strong) Status *status; /** 原创微博整体 */ @property (nonatomic, assign, readonly) CGRect originalViewF; /** 头像 */ @property (nonatomic, assign, readonly) CGRect iconViewF; /** 会员图标 */ @property (nonatomic, assign, readonly) CGRect vipViewF; /** 配图 */ @property (nonatomic, assign, readonly) CGRect photosViewF; /** 昵称 */ @property (nonatomic, assign, readonly) CGRect nameLabelF; /** 时间 */ @property (nonatomic, assign, readonly) CGRect timeLabelF; /** 来源 */ @property (nonatomic, assign, readonly) CGRect sourceLabelF; /** 正文 */ @property (nonatomic, assign, readonly) CGRect contentLabelF; /** cell的高度 */ @property (nonatomic, assign, readonly) CGFloat cellHeight; @end
StatusFrame.m
// // StatusFrame.m // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import "StatusFrame.h" #import "Status.h" #import "User.h" #import "NSString+Size.h" @implementation StatusFrame /** * 设置每一条微博的Frame 及cell的高度 * * @param status 微博模型 */ -(void)setStatus:(Status *)status { _status = status; User *user=status.user; // cell的宽度 CGFloat cellW = [UIScreen mainScreen].bounds.size.width; /* 原创微博 */ /** 头像 */ CGFloat iconWH = 35; CGFloat iconX = StatusCellBorderW; CGFloat iconY = StatusCellBorderW; _iconViewF = CGRectMake(iconX, iconY, iconWH, iconWH); /** 昵称 */ CGFloat nameX = CGRectGetMaxX(self.iconViewF) + StatusCellBorderW; CGFloat nameY = iconY; CGSize nameSize = [user.name sizeWithFont:StatusCellNameFont]; _nameLabelF = (CGRect){{nameX, nameY}, nameSize}; /** 会员图标 */ if (user.isVip) { CGFloat vipX = CGRectGetMaxX(self.nameLabelF) + StatusCellBorderW; CGFloat vipY = nameY; CGFloat vipH = nameSize.height; CGFloat vipW = 14; _vipViewF = CGRectMake(vipX, vipY, vipW, vipH); } /** 时间 */ CGFloat timeX = nameX; CGFloat timeY = CGRectGetMaxY(self.nameLabelF) + StatusCellBorderW; CGSize timeSize = [status.created_at sizeWithFont:StatusCellTimeFont]; _timeLabelF = (CGRect){{timeX, timeY}, timeSize}; /** 来源 */ CGFloat sourceX = CGRectGetMaxX(self.timeLabelF) + StatusCellBorderW; CGFloat sourceY = timeY; CGSize sourceSize = [status.source sizeWithFont:StatusCellSourceFont]; _sourceLabelF = (CGRect){{sourceX, sourceY}, sourceSize}; /** 正文 */ CGFloat contentX = iconX; CGFloat contentY = MAX(CGRectGetMaxY(self.iconViewF), CGRectGetMaxY(self.timeLabelF)) + StatusCellBorderW; CGFloat maxW = cellW - 2 * contentX; CGSize contentSize = [status.text sizeWithFont:StatusCellContentFont maxW:maxW]; _contentLabelF = (CGRect){{contentX, contentY}, contentSize}; /* cell的高度 */ _cellHeight = CGRectGetMaxY(self.contentLabelF)+StatusCellBorderW; } @end
2、接下来,我们需要自定义cell,在cell里添加所有的控件及设置控件的尺寸。
StatusCell.h
// // StatusCell.h // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import <UIKit/UIKit.h> @class StatusFrame; @interface StatusCell : UITableViewCell @property (nonatomic, strong) StatusFrame *statusFrame; + (instancetype)cellWithTableView:(UITableView *)tableView; @end
StatusCell.m
// // StatusCell.m // Weibo // // Created by jiangys on 15/10/24. // Copyright © 2015年 Jiangys. All rights reserved. // #import "StatusCell.h" #import "Status.h" #import "StatusFrame.h" #import "User.h" #import "UIImageView+WebCache.h" @interface StatusCell() /* 原创微博 */ /** 原创微博整体 */ @property (nonatomic, weak) UIView *originalView; /** 头像 */ @property (nonatomic, weak) UIImageView *iconView; /** 会员图标 */ @property (nonatomic, weak) UIImageView *vipView; /** 配图 */ @property (nonatomic, weak) UIImageView *photosView; /** 昵称 */ @property (nonatomic, weak) UILabel *nameLabel; /** 时间 */ @property (nonatomic, weak) UILabel *timeLabel; /** 来源 */ @property (nonatomic, weak) UILabel *sourceLabel; /** 正文 */ @property (nonatomic, weak) UILabel *contentLabel; @end @implementation StatusCell /** * 重写initWithStyle:reuseIdentifier:方法 * 添加所有需要显示的子控件(不需要设置子控件的数据和frame,子控件要添加到contentView中) * 进行子控件一次性的属性设置(有些属性只需要设置一次, 比如字体\固定的图片) */ - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self==[super initWithStyle:style reuseIdentifier:reuseIdentifier]) { /** 原创微博整体 */ UIView *originalView = [[UIView alloc] init]; originalView.backgroundColor = [UIColor whiteColor]; [self.contentView addSubview:originalView]; self.originalView = originalView; /** 头像 */ UIImageView *iconView = [[UIImageView alloc] init]; [originalView addSubview:iconView]; self.iconView = iconView; /** 会员图标 */ UIImageView *vipView = [[UIImageView alloc] init]; vipView.contentMode = UIViewContentModeCenter; [originalView addSubview:vipView]; self.vipView = vipView; /** 配图 */ UIImageView *photosView = [[UIImageView alloc] init]; [originalView addSubview:photosView]; self.photosView = photosView; /** 昵称 */ UILabel *nameLabel = [[UILabel alloc] init]; nameLabel.font = StatusCellNameFont; [originalView addSubview:nameLabel]; self.nameLabel = nameLabel; /** 时间 */ UILabel *timeLabel = [[UILabel alloc] init]; timeLabel.font = StatusCellTimeFont; timeLabel.textColor = [UIColor orangeColor]; [originalView addSubview:timeLabel]; self.timeLabel = timeLabel; /** 来源 */ UILabel *sourceLabel = [[UILabel alloc] init]; sourceLabel.font = StatusCellSourceFont; [originalView addSubview:sourceLabel]; self.sourceLabel = sourceLabel; /** 正文 */ UILabel *contentLabel = [[UILabel alloc] init]; contentLabel.font = StatusCellContentFont; contentLabel.numberOfLines = 0; [originalView addSubview:contentLabel]; self.contentLabel = contentLabel; } return self; } - (void)setStatusFrame:(StatusFrame *)statusFrame { _statusFrame = statusFrame; Status *status = self.statusFrame.status; User *user = status.user; /** 原创微博整体 */ self.originalView.frame = statusFrame.originalViewF; /** 头像 */ self.iconView.frame = statusFrame.iconViewF; [self.iconView sd_setImageWithURL:[NSURL URLWithString:user.profile_image_url] placeholderImage:[UIImage imageNamed:@"avatar_default_small"]]; /** 会员图标 */ if (user.isVip) { self.vipView.hidden = NO; self.vipView.frame = statusFrame.vipViewF; NSString *vipName = [NSString stringWithFormat:@"common_icon_membership_level%d", user.mbrank]; self.vipView.image = [UIImage imageNamed:vipName]; self.nameLabel.textColor = [UIColor orangeColor]; } else { self.nameLabel.textColor = [UIColor blackColor]; self.vipView.hidden = YES; } /** 配图 */ self.photosView.frame = statusFrame.photosViewF; self.photosView.backgroundColor = [UIColor redColor]; /** 昵称 */ self.nameLabel.text = user.name; self.nameLabel.frame = statusFrame.nameLabelF; /** 时间 */ self.timeLabel.text = status.created_at; self.timeLabel.frame = statusFrame.timeLabelF; /** 来源 */ self.sourceLabel.text = status.source; self.sourceLabel.frame = statusFrame.sourceLabelF; /** 正文 */ self.contentLabel.text = status.text; self.contentLabel.frame = statusFrame.contentLabelF; } + (instancetype)cellWithTableView:(UITableView *)tableView { static NSString *ID = @"status"; StatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) { cell = [[StatusCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; } return cell; } @end
3、最后,就是有首页里显示出cell。首页里,显示用到的MJRefresh刷新控件
// // HomeViewController.m // Weibo // // Created by jiangys on 15/10/5. // Copyright (c) 2015年 Jiangys. All rights reserved. // #import "HomeViewController.h" #import "Test1ViewController.h" #import "DropdownMenu.h" #import "TitleMenuViewController.h" #import "TitleButton.h" #import "Status.h" #import "StatusFrame.h" #import "MJRefresh.h" #import "MJExtension.h" #import "AccountTool.h" #import "Account.h" #import "HttpTool.h" #import "StatusCell.h" #import "User.h" @interface HomeViewController ()<DropdownMenuDelegate> /** 微博列表 (里面放的都是HWStatusFrame模型,一个StatusFrame对象就代表一条微博)*/ @property (nonatomic, strong) NSMutableArray *statusFrames; @end @implementation HomeViewController - (NSMutableArray *)statusFrames { if (!_statusFrames) { self.statusFrames = [NSMutableArray array]; } return _statusFrames; } - (void)viewDidLoad { [super viewDidLoad]; // 设置导航栏内容 [self setupNav]; // 获得用户信息(昵称) [self setupUserInfo]; // 集成下拉刷新控件 [self setupDownRefresh]; // 集成上拉刷新控制 [self setupUpRefresh]; } /** * 集成下拉刷新控件 */ - (void)setupDownRefresh { // 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法) self.tableView.header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewStatus)]; // 马上进入刷新状态 [self.tableView.header beginRefreshing]; } - (void)setupUpRefresh { // 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法) self.tableView.footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreStatus)]; } - (void)loadNewStatus { // 1.拼接请求参数 Account *account = [AccountTool getAccount]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"access_token"] = account.access_token; // 取出最前面的微博(最新的微博,ID最大的微博) StatusFrame *firstStatusF = [self.statusFrames firstObject]; if (firstStatusF) { // 若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0 params[@"since_id"] = firstStatusF.status.idstr; } // 2.发送请求 [HttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) { // YSLog(@"--json--%@",json); // 将 "微博字典"数组 转为 "微博模型"数组 NSArray *newStatuses = [Status objectArrayWithKeyValuesArray:json[@"statuses"]]; // 将 HWStatus数组 转为 HWStatusFrame数组 NSArray *newFrames = [self stausFramesWithStatuses:newStatuses]; // 将最新的微博数据,添加到总数组的最前面 NSRange range = NSMakeRange(0, newFrames.count); NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range]; [self.statusFrames insertObjects:newFrames atIndexes:set]; // 刷新表格 [self.tableView reloadData]; // 结束刷新 [self.tableView.header endRefreshing]; // 显示最新微博的数量 [self showNewStatusCount:newStatuses.count]; } failure:^(NSError *error) { YSLog(@"请求失败-%@", error); // 结束刷新刷新 [self.tableView.header endRefreshing]; }]; } /** * 加载更多微博数据 */ - (void)loadMoreStatus { // 1.拼接请求参数 Account *account = [AccountTool getAccount]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"access_token"] = account.access_token; // 取出最后面的微博(最新的微博,ID最大的微博) StatusFrame *lastStatusF = [self.statusFrames lastObject]; if (lastStatusF) { // 若指定此参数,则返回ID小于或等于max_id的微博,默认为0。 // id这种数据一般都是比较大的,一般转成整数的话,最好是long long类型 long long maxId = lastStatusF.status.idstr.longLongValue - 1; params[@"max_id"] = @(maxId); } // 2.发送请求 [HttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) { // 将 "微博字典"数组 转为 "微博模型"数组 NSArray *newStatuses = [Status objectArrayWithKeyValuesArray:json[@"statuses"]]; // 如果没有更多数据,隐藏 if (newStatuses.count==0) { self.tableView.footer.hidden = YES; } // 将 Status数组 转为 StatusFrame数组 NSArray *newFrames = [self stausFramesWithStatuses:newStatuses]; // 将更多的微博数据,添加到总数组的最后面 [self.statusFrames addObjectsFromArray:newFrames]; // 刷新表格 [self.tableView reloadData]; // 结束footer刷新 [self.tableView.footer endRefreshing]; } failure:^(NSError *error) { YSLog(@"请求失败-%@", error); // 结束刷新 [self.tableView.footer endRefreshing]; }]; } /** * 显示最新微博的数量 * * @param count 最新微博的数量 */ - (void)showNewStatusCount:(NSUInteger)count { // 1.创建label UILabel *label = [[UILabel alloc] init]; label.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"timeline_new_status_background"]]; label.width = [UIScreen mainScreen].bounds.size.width; label.height = 35; // 2.设置其他属性 if (count == 0) { label.text = @"没有新的微博数据,稍后再试"; } else { label.text = [NSString stringWithFormat:@"共有%zd条新的微博数据", count]; } label.textColor = [UIColor whiteColor]; label.textAlignment = NSTextAlignmentCenter; label.font = [UIFont systemFontOfSize:16]; // 3.添加 label.y = 64 - label.height; // 将label添加到导航控制器的view中,并且是盖在导航栏下边 [self.navigationController.view insertSubview:label belowSubview:self.navigationController.navigationBar]; // 4.动画 // 先利用1s的时间,让label往下移动一段距离 CGFloat duration = 1.0; // 动画的时间 [UIView animateWithDuration:duration animations:^{ label.transform = CGAffineTransformMakeTranslation(0, label.height); } completion:^(BOOL finished) { // 延迟1s后,再利用1s的时间,让label往上移动一段距离(回到一开始的状态) CGFloat delay = 1.0; // 延迟1s // UIViewAnimationOptionCurveLinear:匀速 [UIView animateWithDuration:duration delay:delay options:UIViewAnimationOptionCurveLinear animations:^{ label.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { [label removeFromSuperview]; }]; }]; // 如果某个动画执行完毕后,又要回到动画执行前的状态,建议使用transform来做动画 } /** * 将Status模型转为StatusFrame模型 */ - (NSArray *)stausFramesWithStatuses:(NSArray *)statuses { NSMutableArray *frames = [NSMutableArray array]; for (Status *status in statuses) { StatusFrame *f = [[StatusFrame alloc] init]; f.status = status; [frames addObject:f]; } return frames; } /** * 设置导航栏内容 */ - (void)setupNav { self.navigationItem.leftBarButtonItem=[UIBarButtonItem itemWithImage:@"navigationbar_friendsearch" highImage:@"navigationbar_friendsearch_highlighted" target:self action:@selector(friendSearch)]; self.navigationItem.rightBarButtonItem=[UIBarButtonItem itemWithImage:@"navigationbar_pop" highImage:@"navigationbar_pop_highlighted" target:self action:@selector(pop)]; /* 中间的标题按钮 */ TitleButton *titleButton = [[TitleButton alloc] init]; NSString *name = [AccountTool getAccount].name; [titleButton setTitle:name?name:@"首页" forState:UIControlStateNormal]; // 监听标题点击 [titleButton addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside]; self.navigationItem.titleView = titleButton; } /** * 标题点击 */ - (void)titleClick:(UIButton *)titleButton { // 1.创建下拉菜单 DropdownMenu *menu = [DropdownMenu menu]; menu.delegate = self; // 2.设置内容 TitleMenuViewController *vc = [[TitleMenuViewController alloc] init]; vc.view.height = 150; vc.view.width = 150; menu.contentController = vc; // 3.显示 [menu showFrom:titleButton]; } /** * 获得用户信息(昵称) */ - (void)setupUserInfo { // 1.拼接请求参数 Account *account = [AccountTool getAccount]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"access_token"] = account.access_token; params[@"uid"] = account.uid; // 2.发送请求 [HttpTool get:@"https://api.weibo.com/2/users/show.json" params:params success:^(id json) { // 标题按钮 UIButton *titleButton = (UIButton *)self.navigationItem.titleView; // 设置名字 User *user = [User objectWithKeyValues:json]; [titleButton setTitle:user.name forState:UIControlStateNormal]; // 存储昵称到沙盒中 account.name = user.name; [AccountTool saveAccount:account]; } failure:^(NSError *error) { YSLog(@"请求失败-%@", error); }]; } -(void)friendSearch { } -(void)pop { Test1ViewController *test1=[[Test1ViewController alloc]init]; test1.title = @"测试2控制器"; [self.navigationController pushViewController:test1 animated:YES]; } #pragma mark - HWDropdownMenuDelegate /** * 下拉菜单被销毁了 */ - (void)dropdownMenuDidDismiss:(DropdownMenu *)menu { UIButton *titleButton = (UIButton *)self.navigationItem.titleView; titleButton.selected = NO;// 让箭头向下 } /** * 下拉菜单显示了 */ - (void)dropdownMenuDidShow:(DropdownMenu *)menu { UIButton *titleButton = (UIButton *)self.navigationItem.titleView; titleButton.selected = YES;// 让箭头向上 } #pragma mark - Table view data source -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.statusFrames.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 获得cell StatusCell *cell = [StatusCell cellWithTableView:tableView]; // 给cell传递模型数据 cell.statusFrame = self.statusFrames[indexPath.row]; return cell; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { StatusFrame *statusFrame=self.statusFrames[indexPath.row]; return statusFrame.cellHeight; } @end
最终效果如下:
章节源代码下载:http://pan.baidu.com/s/1c00SK1q
新浪微博Github:https://github.com/jiangys/Weibo
时间: 2024-11-03 05:28:47