iOS 新浪微博客户端Demo实践之(三)微博主页

来源:互联网 发布:淘宝欧美风店铺 编辑:程序博客网 时间:2024/05/16 02:50

上一篇sina微博Demo已经完成的认证,下面就开始进入微博相关内容的加载及显示。其实主要的工作就是调用微博API 加载相关的json数据,然后进行解析,然后在界面中进行组织好在tableview中进行显示。


这篇博文记录第一个界面--主页

主页中显示当前登录用户及其所关注用户的最新微博,其数据请求用到的API可以是https://api.weibo.com/2/statuses/friends_timeline.json  或者是  https://api.weibo.com/2/statuses/home_timeline.json, 这两个的返回都是一样的,这里还要注意的是HTTP的请求方式,基本上是GET或者POST。

下面详细介绍整个实现的过程:

①数据请求用 GCD的方式,在后台执行数据下载的工作,下载好数据后再在主线程中组织进行UI显示,这样在浏览微博内容的时候就十分流畅了。否则,你同步下载数据,在下载过程中就会阻塞主线程,用户体验十分不佳,刚开始我就是这么做的,后果是,在浏览过程中十分的卡。

-(void) getWeiboData:(int) page {        dispatch_async(dispatch_get_global_queue(0, 0), ^{        hud = [[MBProgressHUD alloc] init];                hud.dimBackground = YES;        hud.labelText = @"正在加载数据...";        [hud show:YES];        [self.view addSubview:hud];                dispatch_sync(dispatch_get_global_queue(0, 0), ^{                        NSURL *url = [NSURL URLWithString:[InfoForSina returnFriendsTimelintURLString:page]];            NSURLRequest *requst = [NSURLRequest requestWithURL:url];            NSData *weiboData = [NSURLConnection sendSynchronousRequest:requst returningResponse:nil error:nil];            //使用JSONKit解析数据            NSString *weiboString = [[NSString alloc] initWithData:weiboData encoding:NSUTF8StringEncoding];                                    NSDictionary *weiboStatusDictionary = [weiboString objectFromJSONString];            if ([_array count] != 0) {                [_array removeAllObjects];            }            [_array addObjectsFromArray:[weiboStatusDictionary objectForKey:@"statuses"]];            for(NSDictionary *dictionary in _array) {                                Status *status = [[Status alloc] init];                status = [status initWithJsonDictionary:dictionary];                [_statusArray addObject:status];            }        });                        dispatch_sync(dispatch_get_main_queue(), ^{            [self.tableView reloadData];            [hud removeFromSuperview];                    });    });    }

以上内容就是我数据请求的方法了,其中有一个page的参数,这个就是下面要提到的继续加载微博的参数,调用API返回微博内容是以页作为单位的,每次放回一页,一页中默认的微博条数是20.当加载完相关数据并且json解析后就在主线程中对tableview进行reloaddata。其中我在一个异步的线程中设置了两个同步线程,这样可以保证数据加载解析后才reload  tableview,而且我在加载的过程中添加了一个MBProgressHUD的提示框,那么在第一个同步线程中添加了,在第二个同步线程中就可以保证被remove。其中的json数据解析就没有什么好讲的了,我是用的jsonkit第三方类库。当然你也可以使用系统自带的解析JSON数据的方法,只是我觉得jsonkit使用比较方便,就一个方法objectFromJSONString。


②微博内容的数据结构也就是model。我是建了一个status的类来处理这部分的内容,其中包含了一个初始化赋值的方法- (Status*)initWithJsonDictionary:(NSDictionary*)dic。

首先我们可以从API调用返回的参数中可以知道,其中的主要的一些返回参数,其中要注意的是retweeted_status这个参数,这个表示转发的微博内容,那么也就是说如果微博是转发的微博,那么就要再对这个参数处理一下,也即再调用上面的那个方法就可以了。

之中有两个要特别处理的是微博来源和微博发送时间。

1、微博来源:

"source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>"
//parse source parameter 处理微博信息来源        NSString *src = [dic objectForKey:@"source"];        NSRange r = [src rangeOfString:@"<a href"];        NSRange end;        //说明是以字符串“<a href”开头的        if (r.location != NSNotFound) {            NSRange start = [src rangeOfString:@"<a href=\""];                        if (start.location != NSNotFound) {                int l = [src length];                NSRange fromRang = NSMakeRange(start.location + start.length, l-start.length-start.location);                end   = [src rangeOfString:@"\"" options:NSCaseInsensitiveSearch                                              range:fromRang];                if (end.location != NSNotFound) {                    r.location = start.location + start.length;                    r.length = end.location - r.location;                    self.sourceUrl = [src substringWithRange:r];                }                else {                    self.sourceUrl = @"";                }            }            else {                self.sourceUrl = @"";            }                       start = [src rangeOfString:@"\">"];            end   = [src rangeOfString:@"</a>"];            if (start.location != NSNotFound && end.location != NSNotFound) {                r.location = start.location + start.length;                r.length = end.location - r.location;                self.source = [src substringWithRange:r];            }            else {                self.source = @"";            }        }        else {            self.source = src;        }


2、微博发送时间

 "created_at": "Tue May 31 17:46:55 +0800 2011"
这是返回的json数据,我们显示的时候只需要显示其中的小时分钟和秒数就可以了。以下是处理的方法:

- (NSString *) getTimeString : (NSString *) string {        NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init];    [inputFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];    [inputFormatter setDateFormat:@"EEE MMM dd HH:mm:ss Z yyyy"];    NSDate* inputDate = [inputFormatter dateFromString:string];        NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];    [outputFormatter setLocale:[NSLocale currentLocale]];    [outputFormatter setDateFormat:@"HH:mm:ss"];    NSString *str = [outputFormatter stringFromDate:inputDate];    return str;}


③tableview cell内容的显示,这个要考虑的问题有四,注意到这部分的内容比较多,所以我新建一个tableviewcell的类来处理这部分的内容。

其一,我们大家都玩过官方的sina微博,可以知道,其微博内容的显示包括:头像,昵称,时间,转发数,评论数,微博内容,微博图片,如果是转发,还需要要转发内容,如果又有图片,那么就不显示前面提到的微博图片,显示转发中的图片就好了,这样是确保一条微博内容中只有一张图片。

其二,在组织这些内容显示的时候,我选择的是以代码的方式创建,这样处理的原因是,微博内容是动态变化的(每一条微博都不一样),所以这样处理比较灵活。但是还需要解决一个问题就是cell重用,这个问题如果不小心处理,会出现的后果是,cell会出现内容重叠,也就是说可能上一个cell的某一个lableview,或者imageview也在下面的cell中重复出现,这部分内容我在前面有一篇博文讲述过这个问题。主要的解决代码是:

 if (cell != nil)    {       [cell removeFromSuperview];//处理重用    }

其三,cell高度的问题,显然,每一条微博占用一个cell,那么自然高度就是不同了,我就需要对每一条微博的高度进行计算以适应,这部分的内容我在前面也有一篇相关动态调整cell 高度的博文。下面这个是我处理的代码:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{    Status *status = [[Status alloc] init];    status = [self.statusArray objectAtIndex:[indexPath row]];        //高度设置    CGFloat yHeight = 70.0;        //微博的内容的高度    CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), MAXFLOAT);    CGSize sizeOne = [status.text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];        yHeight += (sizeOne.height + CELL_CONTENT_MARGIN);        //转发情况    Status  *retwitterStatus = status.retweetedStatus;        //有转发    if (status.hasRetwitter && ![retwitterStatus isEqual:[NSNull null]])    {                //转发内容的文本内容        NSString *retwitterContentText = [NSString stringWithFormat:@"%@:%@",retwitterStatus.screenName,retwitterStatus.text];        CGSize textSize = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), MAXFLOAT);        CGSize sizeTwo = [retwitterContentText sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:textSize lineBreakMode:NSLineBreakByWordWrapping];                yHeight += (sizeTwo.height + CELL_CONTENT_MARGIN);                //那么图像就在转发部分进行显示        if (status.haveRetwitterImage) //转发的微博有图像        {                        yHeight += (120 + CELL_CONTENT_MARGIN);        }    }    //无转发    else    {        //微博有图像        if (status.hasImage) {                                yHeight += (120+ CELL_CONTENT_MARGIN);        }    }    yHeight += 20;    [_heightArray addObject:[NSNumber numberWithFloat:yHeight]];    return yHeight;}

其中我设定图片的高度为120。之前我曾考虑过根据图片的实际大小来设置图片的高度,但是因为我的图片是在GCD异步线程中下载,所以就很难实现,因为tableviewcell的加载是最先的,而图片下载是之后的,除非在上面的函数中同步下载图片就可以做到,但是这显然会阻塞主界面,就会卡卡的了。
其四,微博内容中图片(头像图片和微博图片)的异步下载,同样采用GCD。

__block UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero];            __block UIImage *image = [[UIImage alloc] init];                  dispatch_async(dispatch_get_global_queue(0, 0), ^{                image = [self getImageFromURL:status.thumbnailPic];                                dispatch_async(dispatch_get_main_queue(), ^{                                        CGSize imageSize = CGSizeMake(image.size.width, image.size.height);                                        [imageView setFrame:CGRectMake((CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN *2) - imageSize.width)/2, contentLabel.frame.origin.y + contentLabel.frame.size.height + CELL_CONTENT_MARGIN, imageSize.width, imageSize.height)];                    [imageView setImage:image];                    [[self contentView] addSubview:imageView];                });            });

这段代码就是图片GCD方式处理下载的一个示例,应该比较好理解的了,不用解释了。


④上拉继续加载内容。这里需要处理的是两个问题。

其一,在对这个API进行调用时,返回微博数据是以分页的形式进行的,每页默认的条数是20条,这些都可以在API请求参数中看到,那么我们请求一次,相当于获取了一页,那么如果要继续加载数据我们就可以继续加载第二页,这样一直加载下去就可以了,也就是说调用上面提到的 getWeiboData 方法,改变page这个参数就可以了。

其二,我们的意图是当当前tableview下拉到最底部的时候就进行加载,那么就需要知道何时tableview滑动到底了。

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {    CGPoint contentOffsetPoint = self.tableView.contentOffset;    CGRect frame = self.tableView.frame;    if (contentOffsetPoint.y == self.tableView.contentSize.height - frame.size.height)    {        [self getWeiboData:++_page];    }}

首先我们要知道tableview是继承自scrollveiw的,那么我们可以调用scrollview中的一个代理方法- (void) scrollViewDidScroll:(UIScrollView *)scrollView来处理tableview下拉到最底部的问题,可能刚看到上面的代码可以会有点糊涂,下面就配上一张图片解释一下。


其中tableview.frame指的就是我们视图可见的区域;

tableView.contentSize:The size of the content view.The unit of size is points. The default size is CGSizeZero;这个指的是tableview的内容视图大小,这里要解释一下,我们在tableview中看到是始终是tableview.frame的内容,但是实际是其他内容的视图只是滚出去了而已,就像在上面的视图中那样。

tableview.contentOffset:The point at which the origin of the content view is offset from the origin of the scroll view.这个参数代表的是一个point(点),这个点是相对tableview.contentSize来说的,所以他的坐标应该是从上图最左上方的原点开始计算偏移量的。

不知道这样说是否明白,如果还是不是很清楚,就添加三个NSLog分别输出这些值,就很清楚我说的是什么了。



⑤下拉刷新。其中要考虑的问题有二;

其一,刷新视图。一般的你可以在navigation item上面添加一个button进行刷新。但是更加常规的做法是下拉刷新,我这里处理下拉刷新是使用了iOS6新增加的一个特性UIRefreshControl有了它,下拉刷新的实现就十分简单了,也可以使用一个 EGOTableViewPullRefresh的第三方类库也可以实现。

首先看看官方文档对它的描述。

A UIRefreshControl object provides a standard control that can be used to initiate the refreshing of a table view’s contents. You link a refresh control to a

 table through

 an associated table view controller object. The table view controller handles the work of adding the control to the table’s visual appearance and managing

 the display 

of that control in response to appropriate user gestures.

UIRefreshControl对象提供了一个标准的刷新控制器,可用于刷新tableview中的内容。刷新控制器连结到一个tableview。可以通过控制适当的用户手势(下拉)响应的刷新处理工作。

In addition to assigning a refresh control to a table view controller’s refreshControl property, you must configure the target and action of the control itself. 

The control does not initiate the refresh operation directly. Instead, it sends the UIControlEventValueChanged event when a refresh operation should occur. 

You must assign an action 

method to this event and use it to perform whatever actions are needed.

除了创建一个refreshcontrol控制器的实例,还必须配置控制本身的目标和行动。控制器本身不直接处理启动刷新的具体操作。相反,当你进行下拉操作时,它发送UIControlEventValueChanged的刷新操作事件,您必须指定此事件的操作方法,并用它来执行所需的任何操作。

The UITableViewController object that owns a refresh control is also responsible for setting that control’s frame rectangle. Thus, you do not need to manage the size

 or position of a refresh control directly in your view hierarchy.

UITableViewController自动处理相关的刷新视图显示,不需要用户操心。


他的属性和方法也很少,用法相当简单。

Initializing a Refresh Control
– init
Accessing the Control Attributes
  tintColor  property
  attributedTitle  property
Managing the Refresh Status
– beginRefreshing
– endRefreshing
  refreshing  property   BOOL

其二,对刷新数据的处理,我是采用清空微博数据数组,从新调用API请求数据的方式,不过这样处理会有一个隐藏的bug,就是如果tableview还没有彻底加载好视图,你就下拉进行刷新,那么就会出现就会崩溃,因为你刷新的时候清空了所有的数据。如果我重新初始化这个可变数组的话,这个问题好像就可以解决了。下面代码展示一下。

-(void)addRefreshViewController{    self.refreshControl = [[UIRefreshControl alloc] init];    self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"下拉刷新"];    [self.refreshControl addTarget:self action:@selector(RefreshViewControlEventValueChanged) forControlEvents:UIControlEventValueChanged];}-(void)RefreshViewControlEventValueChanged{     [self.refreshControl beginRefreshing];     self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"刷新中..."];    [self performSelector:@selector(loadData) withObject:nil afterDelay:1.0f];}-(void)loadData{    //    [_statusArray removeAllObjects];    _statusArray = [[NSMutableArray alloc] init];    //重新回到第一页    _page = 1;    [self getWeiboData:_page];        if (self.refreshControl.refreshing == true) {                [self.refreshControl endRefreshing];        self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"下拉刷新"];    }    }

这个代码层次应该还是比较清楚的了,第一个方法,创建一个refreshcontrol实例,并指定响应方法;第二个方法,启动刷新后的视图处理,并指定刷新的具体操作;第三个方法,重新加载微博数据继续刷新,结束刷新视图。