微博閱讀器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改爲自己對應的):

下載鏈接


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章