微博阅读器demo(二) 微博列表

来源:互联网 发布:淘宝超a鞋店推荐 编辑:程序博客网 时间:2024/04/28 15:46

微博阅读器demo实现的第二部分: 微博列表,这也是本demo的主要部分。

微博列表通过一个UITableView来实现,所要面临的问题有如下几个:

1.调用开放平台的微博API,获取json数据并解析;

2.UITableViewCell的布局设计;

3.下拉刷新和上拉加载更多。

实现的效果如下:



一、调用开放平台的微博API,获取json数据并解析

本demo主要调用https://api.weibo.com/2/statuses/friends_timeline.json 这个API获取当前登录用户及其所关注用户的最新微博,

查阅api的介绍,这个接口采用get方法,参数如下:

 必选类型及范围说明sourcefalsestring采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。access_tokenfalsestring采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。since_idfalseint64若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0。max_idfalseint64若指定此参数,则返回ID小于或等于max_id的微博,默认为0。countfalseint单页返回的记录条数,最大不超过100,默认为20。pagefalseint返回结果的页码,默认为1。base_appfalseint是否只获取当前应用的数据。0为否(所有数据),1为是(仅当前应用),默认为0。featurefalseint过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。trim_userfalseint返回值中user字段开关,0:返回完整user字段、1:user字段仅返回user_id,默认为0。

我们只要指定access_token和page这两个参数,其他默认。count默认为20,也就是说我们一次能获得20条微博。为了不阻塞主线程,

访问网络的操作都采用GCD,

访问成功之后得到NSData,并对其解析。

说到这里,我要先介绍一下我的微博类(WBStatuses)的模型

#import <Foundation/Foundation.h>#import "WBRetweetedStatus.h"@interface WBStatuses : NSObject@property NSString * profile_image; // 微博头像地址@property NSString * userName;      // 用户名@property NSString * from;          // 微博来源@property NSString * text;          // 微博正文@property NSString * idstr;         // 微博id@property NSString * createAt;      // 微博创建时间@property NSMutableArray * thumbnailPictureUrls;  // 缩略图地址@property NSMutableArray * bmiddlePictureUrls;    // 中等大小图片地址@property NSMutableArray * largePictureUrls;      // 原图地址@property WBRetweetedStatus * retweetedStatus;    // 转发的微博@property bool hasRetweetedStatuses;              // 是否含有转发微博@end

针对微博API所返回的json数据的格式,解析如下:

#import "WBStatusesImpl.h"@implementation WBStatusesImpl-(NSMutableArray *) httpRequestWithPage:(int) page{    HttpHelper * httpHelper = [[HttpHelper alloc]init];    UrlHelper * urlHelper = [[UrlHelper alloc]init];    NSString * url = [NSString stringWithFormat:[urlHelper urlForKey:@"friends_timeline"],[urlHelper accessToken],page];       // 获取完整地址    NSLog(@"%@",url);    NSData * data = [httpHelper SynchronousGetWithUrl:url];  // get 方法访问网络    NSMutableArray * statuses = [[NSMutableArray alloc]init];  // 存放20条微博的数组    if(data)       // 获取数据成功    {        NSDictionary * dic =[NSJSONSerialization JSONObjectWithData:data            options:NSJSONReadingMutableLeaves error:nil];        NSArray * statusesArray =[dic objectForKey:@"statuses"];     // 讲NSData解析成数组                for(NSDictionary * d in statusesArray)   // 循环解析每条微博        {            NSString * tem;            NSArray * urlTem;            NSDictionary * user = [d objectForKey:@"user"];    // 获得用户信息            NSDictionary * retweetedStatus = [d objectForKey:@"retweeted_status"];  // 转发的微博            WBStatuses * wbstatuses = [[WBStatuses alloc]init];            wbstatuses.profile_image = [user objectForKey:@"profile_image_url"];                wbstatuses.userName =[user objectForKey:@"name"];            wbstatuses.text=[d objectForKey:@"text"];            wbstatuses.idstr = [d objectForKey:@"idstr"];            wbstatuses.createAt = [d objectForKey:@"created_at"];            wbstatuses.createAt = [DateHelper timePassedSinceDateString:wbstatuses.createAt];  // 计算发微博的时间到现在的时间间隔            if(retweetedStatus!=nil)    //解析转发微博,方法与微博一致            {                wbstatuses.hasRetweetedStatuses = true;                wbstatuses.retweetedStatus.text = [retweetedStatus objectForKey:@"text"];                NSDictionary * retweetedUser = [retweetedStatus objectForKey:@"user"];                wbstatuses.retweetedStatus.userName = [retweetedUser objectForKey:@"name"];                wbstatuses.retweetedStatus.createAt = [retweetedStatus objectForKey:@"created_at"];                urlTem =[d objectForKey:@"pic_urls"];                for(NSDictionary * urlDic in urlTem)                {                    NSString * str =[urlDic objectForKey:@"thumbnail_pic"];                    [wbstatuses.retweetedStatus.thumbnailPictureUrls addObject:str];                    [wbstatuses.retweetedStatus.bmiddlePictureUrls addObject:[str stringByReplacingOccurrencesOfString:@"sinaimg.cn/thumbnail/" withString:@"sinaimg.cn/bmiddle/"]];                    [wbstatuses.retweetedStatus.largePictureUrls addObject:[str stringByReplacingOccurrencesOfString:@"sinaimg.cn/thumbnail/" withString:@"sinaimg.cn/large/"]];                    //        NSLog(@"%@",str);                }            }                        urlTem =[d objectForKey:@"pic_urls"];            for(NSDictionary * urlDic in urlTem)    // 解析微博图片地址,缩略图与其他图地址只是前面部分不同            {                NSString * str =[urlDic objectForKey:@"thumbnail_pic"];                [wbstatuses.thumbnailPictureUrls addObject:str];                [wbstatuses.bmiddlePictureUrls addObject:[str stringByReplacingOccurrencesOfString:@"sinaimg.cn/thumbnail/" withString:@"sinaimg.cn/bmiddle/"]];                [wbstatuses.largePictureUrls addObject:[str stringByReplacingOccurrencesOfString:@"sinaimg.cn/thumbnail/" withString:@"sinaimg.cn/large/"]];            }            tem = [d objectForKey:@"source"];            wbstatuses.from=@"来自:";            tem = [[[tem substringToIndex:[tem length]-4] componentsSeparatedByString:@">"] lastObject];            wbstatuses.from = [wbstatuses.from stringByAppendingString:tem];            [statuses addObject:wbstatuses];        }        return statuses;    }    return nil;}@end

二、UITableViewCell的布局设计

很明显,cell需要采用自定义的模式。观察发现每个微博cell就只有头像,用户名,时间,来源的位置和大小相对固定,可以在storyboard中先确定。

微博正文高度不定,图片个数不定,需要动态加载。同时图片的获取需要采用GCD。

首先,每个cell的高度都不同,要在微博列表对应的控制类中实现UITableView的委托方法

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 来确定每个cell的高度,这其中有一些要注意

的在我另一篇博客(UITableView中heightForRowAtIndexPath 产生 EXC_BAD_ACCESS 的原因)说明。此外,因为UITableViewCell的重用机制,并且

每个cell都动态添加图片和正文,这将导致cell中的内容错位(即上一个cell的内容出现在下一个cell中)如下图所示:


解决的办法是给每个动态加载的图片设置一个标记然后在

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath中遍历cell的子视图,

若为动态加载的就将其remove,然后再重新加载新的子视图:

    static NSString *CellIdentifier = @"WBStatusesCell";    WBStatusesCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];  // 重用        for(UIView * v in [cell subviews])  //  遍历子视图    {        if(v.tag == 1)   // 标记为1,是动态加载的,将其remove        {            [v removeFromSuperview];        }    }    WBStatuses *statuses = [self.statuses objectAtIndex:indexPath.row];  // 指定位置的微博数据    [cell contentWithWBStatuses:statuses];   // 重新加载

还有就是图片的下载,因为不能每一次加载都重新下载图片,所以,下载过的图片就存放在一个NSDictionary中以图片地址作为键值,

重新加载的时候,先查看NSDictionary中有没有对应的图片,有就直接用,没有再下载:

    UIImage *image = [self.imageDictionary objectForKey:statuses.profile_image];    cell.profile_image.image = image;    if(image==nil)    {        cell.profile_image.image  = nil;        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{            cell.profile_image.image =[UIImage  imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:statuses.profile_image]]];            [self.imageDictionary setValue:cell.profile_image.image forKey:statuses.profile_image];        });    }

三、下拉刷新和上拉加载更多

下拉刷新采用ios自带的UIRefreshControl,UIScrollerView及其子类都有一个refreshControl属性用于下拉刷新,只需在viewDidLoad中对refreshControl

进行设置即可:

    self.refreshControl = [[UIRefreshControl alloc]init];    self.refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:@"下拉刷新"];    [self.refreshControl addTarget:self action:@selector(Refresh) forControlEvents:UIControlEventValueChanged];
将要执行的代码放在Refresh方法中

-(void)Refresh{    [self.refreshControl beginRefreshing];    self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"加载中..."];    [self performSelector:@selector(loadData) withObject:nil afterDelay:1.0f];}-(void)loadData{    if (self.refreshControl.refreshing == true)    {        [self performHttpRequestWithPage:1];            }}

加载完成后,修改refreshControl的属性:

        if(page==1&&self.refreshControl.refreshing)        {            [self.refreshControl endRefreshing];            self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"下拉刷新"];        }

上拉加载更多其实就是拉到最底部显示一个按钮,点击按钮就加载更多内容:


因为每个cell的高度不固定,所以整个tableView的contentSize也就不固定,加载按钮的位置也要动态变化。每次有新的cell加入时contenSize的高度就改变,

按钮的位置(frame)就要随之改变,相反,contenSize的高度不变,按钮的位置就不应该变化。所以,我们通过tableView的tag来控制按钮的位置是否需要改变。

viewDidLoad中设置并添加按钮:

    UIButton * btn =[[UIButton alloc]init];    [btn addTarget:self action:@selector(loadMore) forControlEvents:UIControlEventTouchDown];    [btn setTitle:@"加载更多" forState:UIControlStateNormal];    [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];    self.loadMoreButton = btn;    [self.tableView addSubview:self.loadMoreButton];    self.tableView.tag = NEED_READD_BUTTON;

UITableView也是一个UIScrollView,可以实现方法- (void)scrollViewDidScroll:(UIScrollView *)scrollView判断是否滑到底部:

- (void) scrollViewDidScroll:(UIScrollView *)scrollView{        CGPoint contentOffsetPoint = self.tableView.contentOffset;  // 偏移点        CGRect frame = self.tableView.frame;        if (contentOffsetPoint.y == self.tableView.contentSize.height - frame.size.height) // 滑到了底部    {        if(self.tableView.tag == NEED_READD_BUTTON)  // 是否重新改变按钮位置        {            self.tableView.contentSize = CGSizeMake(self.tableView.contentSize.width, self.tableView.contentSize.height+40);   //  增加contenSize的高度,用于放置按钮            self.loadMoreButton.frame = CGRectMake(0,self.tableView.contentSize.height-40,self.tableView.contentSize.width,40);    //  改变按钮的位置            self.tableView.tag = NOT_READD_BUTTON;  //  将tableView 标记改为不需改变        }    }}

数据加载完成,回到主线程,改变tableView的标记,将其隐藏,并reloadData:

        dispatch_async(dispatch_get_main_queue(), ^{            self.tableView.tag = NEED_READD_BUTTON;            self.loadMoreButton.frame = CGRectMake(0, 0, 0, 0);            [self.tableView reloadData];        });


微博阅读器demo的主要部分就完成了,下面是我的源码(注意将WBForWang-Prefix.pch文件中的REDIRECT_URI、 APP_KEY以及APP_SECRECT改为自己对应的):

下载链接


0 0
原创粉丝点击