上一篇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執行個體,並指定回應程式法;第二個方法,啟動重新整理後的視圖處理,並指定重新整理的具體操作;第三個方法,重新載入微博資料繼續重新整理,結束重新整理視圖。