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

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

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

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

2.UITableViewCell的布局设计;

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

实现的效果如下:



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

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

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

  必选 类型及范围 说明
source false string 采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。
access_token false string 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。
since_id false int64 若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0。
max_id false int64 若指定此参数,则返回ID小于或等于max_id的微博,默认为0。
count false int 单页返回的记录条数,最大不超过100,默认为20。
page false int 返回结果的页码,默认为1。
base_app false int 是否只获取当前应用的数据。0为否(所有数据),1为是(仅当前应用),默认为0。
feature false int 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。
trim_user false int 返回值中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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值