@(iOS 項目實戰)[項目實戰]
- 作者: Liwx
- 郵箱: 1032282633@qq.com
目錄
- 10.項目實戰 百思不得姐 設置圖片,視頻,聲音圖片控件的內容
- 1.計算帖子cell的高度(暫不考慮中間圖片控件)
- 計算帖子cell高度的方式一(使用字典緩存方式)
- 計算帖子cell高度的方式一(通過模型數據計算cell高度)
- tableView估算高度的簡單使用
- 2.計算中間圖片控件的frame
- 計算中間圖片控件的frame
- 3.使用xib自定義中間圖片控件(圖片,聲音,視頻類型的圖片控件)
- 使用xib自定義三種圖片控件
- tableView補充
- 4.設置聲音,視頻圖片控件的內容
- 設置聲音圖片控件的內容
- 封裝分類UIImageView+Image,實現通過判斷網絡狀態下載對應的圖片
- 5.設置圖片控件的內容(長圖,動態圖,普通圖)
- 通過url判斷是靜態圖還是動態圖
- 設置圖片控件內容
- 存在問題
- 補充
- xib命名時需注意
- SDWebImage補充
1.計算帖子cell的高度(暫不考慮中間圖片控件)
因為中間圖片控件顯示的圖片的尺寸(寬高)是由服務器返回的數據提供,所以此次不能使用自動計算高度的方式,需手動計算cell高度.
-
使用xib布局帖子的cell
使用xib布局帖子的cell.png
計算帖子cell高度的方式一(使用字典緩存方式)
-
方式一: 使用
字典緩存
所有模型對應的cell的高度(不推薦)- 使用模型的內存地址作為字典的key.
// 將內存地址轉換成字符串,作為字典的key NSString *key = [NSString stringWithFormat:@"%p", topicItem];
- 也可以使用模型的description方法來獲取類名+內存地址,作為字典的key.
// description方法來獲取類名+內存地址,作為字典的key NSString *key = topicItem.description;
- 緩存cell的高度,無需每次都計算cell高度
- 如果
cell高度不為0
,表示已經計算過cell高度,無需再重復計算.
- 如果
計算帖子cell高度的方式一(通過模型數據計算cell高度)
使用模型數據計算cell高度,為模型額外添加cellHeight屬性,用戶存放每個模型對應cell的高度,更利于程序封裝性.
- 方式二: 通過模型數據計算cell高度
- 1.為WXTopicItem模型
添加cellHeight屬性
,用于存放通過模型數據計算的cell高度. - 2.重寫cellHeight的get方法,在方法中實現通過模型數據計算cell高度.使用cellHeight累計cell高度.
- 1.頂部view高度55
- 2.文字高度
- 計算文字高度方式一: (過期)使用
sizeWithFont:constrainedToSize:
方法,參數Size表示限制文字顯示的范圍
. - 計算文字高度方式二: 使用
boundingRectWithSize:options:attributes:attributes context:
方法計算文字高度,options
參數需設置為NSStringDrawingUsesLineFragmentOrigin
.
- 計算文字高度方式一: (過期)使用
- 3.中間顯示的圖片控件高度(此處暫不考慮)
- 4.最熱評論高度 = 最熱評論標題高度(20) + 最熱評論內容高度
- 5.底部工具條高度
- 底部工具條的高度包括下面灰色分割線高度10
實現分割線方法: 設置tableView的背景色為灰色,重寫cell的setFrame:方法,設置每個cell之間的間隔為10
- 底部工具條的高度包括下面灰色分割線高度10
- 1.為WXTopicItem模型
// WXAllViewController.m控制器的viewDidLoad方法中設置tableView背景色,并取消原來的分割線.
self.tableView.backgroundColor = WXColor(206, 206, 206);
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// WXTopicItem.m文件中重寫,設置每個cell之間的間隔為10
- (void)setFrame:(CGRect)frame
{
frame.size.height -= WXMargin;
[super setFrame:frame];
}
-
設置cell的背景圖片,需在Assets.xcassets中設置mainCellBackground背景圖片為可拉伸圖片,或者可以使用代碼方式拉伸背景圖片最中間的點.
-
注意
: 每個模塊間都有10的間距. -
性能優化
: 每次新的cell重新進入視野時,系統會重新調用heightRowAtIndexPath方法
,獲取新的cell的高度. - 此處cell的高度是通過模型數據計算的,無需每次都重新計算cell高度.如果cell高度已經計算過,則直接返回cell高度.
- 使用模型數據計算cell高度核心參考代碼
// ---------------------------------------------------------------------------- // WXTopicItem.m中重寫cellHeight方法,計算cell高度 - (CGFloat)cellHeight { // 如果已經計算過cell的高度,無需再計算 if (_cellHeight) return _cellHeight; // ------------------------------------------------------------------------ // 1.頂部view高度55 _cellHeight += 55; // ------------------------------------------------------------------------ // 2.文字高度 // 2.1 文字最大寬度 = 屏幕寬度 - 2 * 間距 CGFloat textMaxW = screenW - 2 * WXMargin; _cellHeight += [self.text boundingRectWithSize:CGSizeMake(textMaxW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height + WXMargin; // ------------------------------------------------------------------------ // 3.最熱評論高度 = 最熱評論標題高度(20) + 最熱評論內容高度 // 3.1 取出最熱評論 NSDictionary *cmt = self.top_cmt.firstObject; if (cmt) { // 3.2 最熱評論標題高度20 _cellHeight += 20; // 3.3 最熱評論內容高度 NSString *username = cmt[@"user"][@"username"]; NSString *content = cmt[@"content"]; if (content.length == 0) { content = @"[語音評論]"; } NSString *cmtText = [NSString stringWithFormat:@"%@ : %@", username, content]; _cellHeight += [cmtText boundingRectWithSize:CGSizeMake(textMaxW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size.height + WXMargin; } // ------------------------------------------------------------------------ // 4.底部工具條高度 _cellHeight += 35 + WXMargin; return _cellHeight; }
-
tableView估算高度的簡單使用
- 設置tableView中的cell估算高度的兩種方式
- 方式一: 統一設置tableView所有cell估算高度
self.tableView.estimatedRowHeight = 100;
- 方式二: 使用代理方法分別設置tableView中每個cell的估算高度
tableView會先多次調用estimatedHeightForRowAtIndexPath:
方法,再調用heightRowAtIndexPath
方法.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;
如果沒有使用估算高度,tableView顯示前會先多次調用heightRowAtIndexPath方法,之后每次cell顯示時又會調用heightRowAtIndexPath方法. - 會出現滾動tableView時,
滾動條的長度
會隨tableView的滾動而不斷變化.
- 方式一: 統一設置tableView所有cell估算高度
2.計算中間圖片控件的frame
計算中間圖片控件的frame
- 1.為WXTopicItem模型添加圖片
高度和寬度屬性
,為模型額外添加中間圖片控件的frame屬性centerFrame
. - 2.在cellHeight的get方法中添加計算中間圖片控件高度和frame的處理,計算中間圖片控件高度和centerFrame的核心參考代碼如下
- 如果是非文字段子,累計中間圖片控件高度.
- 計算中間圖片控件等比例顯示圖片
- 計算中間圖片控件的centerFrame屬性
- _cellHeight屬性累計圖片控件高度.
// ------------------------------------------------------------------------
// 3.中間圖片控件高度,等比例填充方式顯示圖片
// 3.1 判斷如果不是文字段子,累加圖片控件高度
if (self.type != WXTopicTypeWord) {
// 3.2 計算圖片控件實際的frame
// centerW / centerH = self.width / self.height;
CGFloat centerW = textMaxW;
CGFloat centerH = centerW * self.height / self.width;
CGFloat centerX = WXMargin;
CGFloat centerY = _cellHeight;
self.centerFrame = CGRectMake(centerX, centerY, centerW, centerH);
_cellHeight += centerH + WXMargin;
}
3.使用xib自定義中間圖片控件(圖片,聲音,視頻類型的圖片控件)
據分析,三種類型的圖片控件都是用xib方式創建,無需多處都用[[NSBundle mainBundle] loadNibNamed:方法從xib創建圖片控件,可以考慮封裝UIView+Init的分類.
UIView+Init分類中封裝加載同名xib的類方法
.這樣就無需在每個圖片控件類中實現快速從xib創建控件的類方法.參考代碼如下.
// ----------------------------------------------------------------------------
// 從xib創建view
+ (instancetype)wx_viewFromXib
{
return [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil].lastObject;
}
使用xib自定義三種圖片控件
- 1.使用xib方式創建圖片,聲音,視頻類型的圖片控件類.
- 2.為區分三種不同控件,粗略用不同背景色來區分三種圖片控件.在xib中分別設置圖片,聲音,視頻的圖片控件背景色為紅色,綠色,藍色.
- 3.
避免重復添加圖片控件
,使用懶加載
的方式將三種類型的圖片控件添加到cell的contentView
中. - 4.
避免重復利用時,重復添加不同類型的圖片控件,出現圖片控件既有聲音圖片控件,又有視頻圖片控件
.解決方案:在cell的模型set方法setTopicItem:中控制不同類型的帖子的圖片控件是否要隱藏/顯示
.
// WXTopicCell.m中的setTopicItem:方法中添加控制中間圖片控件的隱藏/顯示
// ------------------------------------------------------------------------
// 設置中間圖片控件的隱藏/顯示
if (self.topicItem.type == WXTopicTypePicture) { // 圖片
self.pictureView.hidden = NO;
self.voiceView.hidden = YES;
self.videoView.hidden = YES;
} else if (self.topicItem.type == WXTopicTypeVoice) { // 聲音
self.pictureView.hidden = YES;
self.voiceView.hidden = NO;
self.videoView.hidden = YES;
} else if (self.topicItem.type == WXTopicTypeVideo) { // 視頻
self.pictureView.hidden = YES;
self.voiceView.hidden = YES;
self.videoView.hidden = NO;
} else if (self.topicItem.type == WXTopicTypeWord) { // 文字
self.pictureView.hidden = YES;
self.voiceView.hidden = YES;
self.videoView.hidden = YES;
}
- 5.在
layoutSubviews
方法中重新布局不同類型的帖子要顯示的圖片控件- 只有cell顯示時才會調用cell的layoutSubviews方法進行布局.最好在layoutSubviews方法中設置子控件的frame.
// ----------------------------------------------------------------------------
// 布局子控件
- (void)layoutSubviews
{
[super layoutSubviews];
// ------------------------------------------------------------------------
// 布局中間圖片控件
if (self.topicItem.type == WXTopicTypePicture) { // 圖片
self.pictureView.frame = self.topicItem.centerFrame;
} else if (self.topicItem.type == WXTopicTypeVoice) { // 聲音
self.voiceView.frame = self.topicItem.centerFrame;
} else if (self.topicItem.type == WXTopicTypeVideo) { // 視頻
self.videoView.frame = self.topicItem.centerFrame;
}
}
-
帖子初步運行效果
帖子初步運行效果.gif
- 如果不在layoutSubviews中設置子控件的frame,可能會出現布局異常.
- 如果
不在layoutSubviews里面設置子控件的frame
,有可能會受到autoresizingMask的影響
,導致子控件的尺寸發生變化
. - 如果要
消除autoresizingMask的影響
,可以設置view.autoresizingMask = UIViewAutoresizingNone;
. -
注意
: UIView創建時,autoresizingMask默認是拉伸寬高. - 以后如果遇到布局不符合預計的效果(
不受控制
)時,應該想到很有可能是autoresizing
問題. -
建議
: 最好在layoutSubviews方法中設置子控件的frame.
- 如果
tableView補充
-
tableView的代理方法調用順序
- 1.numberOfRowInSection
- 2.heightForRowAtIndexPath
- 3.cellForRowAtIndexPath
-
cell的layoutSubviews方法只有在cell即將顯示的時候才會調用,
- 1.numberOfRowInSection
- 2.heightForRowAtIndexPath
- 3.cellForRowAtIndexPath
- 4.最好調用cell的layoutSubviews方法.
4.設置聲音,視頻圖片控件的內容
設置聲音和視頻圖片控件的功能實現方式基本一致.所以只對聲音圖片控件的實現進行描述,不再對視頻圖片控件的實現進行累述.
-
使用xib布局聲音和視頻圖片控件
- 在xib中設置占位圖片(顯示百思不得姐圖片)
- 設置占位圖片的
imageView的contentMode為Aspect Fit
,按比例填充.
- 設置占位圖片的
xib布局聲音和視頻圖片控件.png - 在xib中設置占位圖片(顯示百思不得姐圖片)
設置聲音圖片控件的內容
1.給WXTopicItem模型添加播放數量,聲音文件的播放時長,視頻文件的播放時長,小圖,大圖,中圖等屬性.
2.聲音圖片控件WXTopicVoiceView對外提供模型數據
3.在WXTopicCell.m文件的模型set方法setTopicItem:中,
控制voiceView的隱藏/顯示處
,為voiceView提供模型數據self.voiceView.topicItem = topicItem;
.-
4.重寫模型數據的set方法,設置聲音view的數據
- 在模型的set方法設置圖片,因該功能多處都會用到,故考慮封裝分類.
- 封裝通過網絡狀態判斷要下載大圖或小圖,顯示的占位圖片的
分類UIImageView+Image
. - 處理并設置播放數量,播放時長.
- 模型set方法實現代碼
// ---------------------------------------------------------------------------- // 重寫模型數據的set方法,設置聲音view的數據 - (void)setTopicItem:(WXTopicItem *)topicItem { _topicItem = topicItem; // ------------------------------------------------------------------------ // 1.設置圖片 [self.imageView wx_setLargetImage:topicItem.image1 smallImage:topicItem.image0 placeholder:nil]; // ------------------------------------------------------------------------ // 2.設置播放數量 if (topicItem.playcount >= 10000) { self.playCountLabel.text = [NSString stringWithFormat:@"%.1f萬播放",topicItem.playcount / 10000.0]; } else { self.playCountLabel.text = [NSString stringWithFormat:@"%zd播放", topicItem.playcount]; } // ------------------------------------------------------------------------ // 3.設置播放時長 NSInteger minutes = topicItem.voicetime / 60; NSInteger seconds = topicItem.videotime % 60; self.voiceTimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd", minutes, seconds]; }
封裝分類UIImageView+Image,實現通過判斷網絡狀態下載對應的圖片
-
封裝通過網絡狀態判斷要下載大圖或小圖,顯示的占位圖片的
分類UIImageView+Image
.-
注意
: 判斷網絡狀態僅在真機才有效,模擬器不能用.使用預編譯宏TARGET_IPHONE_SIMULATOR
,TARGET_OS_IPHONE
來判斷當前要運行環境是模擬器
還是真機
.
#if TARGET_IPHONE_SIMULATOR //模擬器 // 模擬器環境實現代碼 #elif TARGET_OS_IPHONE //真機 // 真機環境實現代碼 #endif
如果是模擬器環境,直接用大圖顯示
-
如果是真機環境下通過
判斷網絡狀態
來確定要下載大圖或小圖.- 1.優先判斷是否已經下載過大圖 ,如果已下載過大圖,直接顯示大圖
- 2.如果為下載過大圖,使用AFNetworking的
AFNetworkReachabilityManager
判斷網絡狀態,用于確定要下載大圖/小圖.
// AFNetworkReachabilityManager判斷當前網絡狀態,如果網絡狀態變化,會執行block的任務 [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status)); }]; // 必須啟動監聽 [[AFNetworkReachabilityManager sharedManager] startMonitoring];
- 3.如果AFNetworkReachabilityManager監聽到當前網絡狀態變化,則根據不同網絡狀態下載不同圖片.
- 1.如果是wifi,則下載大圖;
- 2.如果是手機自帶3G/4G網絡,則下載小圖;
- 3.如果沒有網絡,先判斷是否已下載過小圖,如果已下載過小圖,則顯示小圖.
- 4.若沒有下載過小圖,則根據需求如果有占位圖片顯示占位圖片,如果沒有占位圖片則清空圖片.
- 通過
AFNetworkReachabilityManager
判斷網絡狀態下載對應的圖片實現參考代碼
// ---------------------------------------------------------------------------- // 通過網絡狀態判斷要下載大圖或小圖,顯示的占位圖片 - (void)wx_setLargetImage:(NSString *)largetImageUrl smallImage:(NSString *)smallImageUrl placeholder:(UIImage *)placeholder { #if TARGET_IPHONE_SIMULATOR //模擬器 // ------------------------------------------------------------------------ // 1.1 模擬器不同判斷網絡狀態,直接用大圖顯示 [self sd_setImageWithURL:[NSURL URLWithString:largetImageUrl] placeholderImage:placeholder]; #elif TARGET_OS_IPHONE //真機 // ------------------------------------------------------------------------ // 1.1 在真機環境下通過判斷網絡狀態來確定要下載大圖或中圖或小圖 // 如果已經下載過大圖,優先顯示大圖 UIImage *largeImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:largetImageUrl]; if (largeImage) { // -------------------------------------------------------------------- // 已下載過大圖,直接顯示大圖 self.image = largeImage; } else { // -------------------------------------------------------------------- // 未下載過大圖,使用AFNetworking判斷網絡狀態,用于確定要下載大圖/小圖 __weak AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager]; __weak typeof(self) weakSelf = self; [mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { // NSLog(@"%zd", mgr.networkReachabilityStatus); if (mgr.isReachableViaWiFi) { // 如果是wifi,下載大圖 [weakSelf sd_setImageWithURL:[NSURL URLWithString:largetImageUrl]]; } else if (mgr.isReachableViaWWAN) { // 如果是手機自帶3G/4G網絡,下載小圖 [weakSelf sd_setImageWithURL:[NSURL URLWithString:smallImageUrl]]; } else { // 沒有網絡,如果有占位圖片顯示占位圖片,如果沒有占位圖片則清空圖片 // 判斷是否已下載過小圖 UIImage *smallImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:smallImageUrl]; if (smallImage) { weakSelf.image = smallImage; } else { weakSelf.image = nil; // 根據需求顯示占位圖片或清空圖片 } } }]; [mgr startMonitoring]; } #endif }
-
- 聲音,視頻類型的帖子運行效果
- 紅色部分是還未實現的圖片控件.
5.設置圖片控件的內容(長圖,動態圖,普通圖)
通過url判斷是靜態圖還是動態圖
// gif的url,注意大小寫.
http://ww4.sinaimg.cn/large/61129224jw1f139h1eop6g207804ax6q.GIF
http://ww4.sinaimg.cn/large/61129224jw1f139h1eop6g207804ax6q.jpg
-
判斷圖片是靜態圖還是動態圖
- 方法一: 先將url轉成小寫, 使用lowercaseString
- 通過后綴名判斷, 使用hasSuffix方法
if ([topic.image1.lowercaseString hasSuffix:@"gif"]) ;
- 方法二; 還可以使用pathExtension方法
if ([topic.image1.pathExtension.lowercaseString isEqualToString:@"gif"]) ;
- 方法三: 看下服務器是否有返回圖片是否為動態圖.服務器有返回參數
is_gif
,該參數是用來判斷是靜態或動態圖. - 方法四: 使用SDWebImage中
NSData的分類方法sd_contentTypeForImageData:方法
判斷圖片是靜態圖還是動態圖.(圖片數據的第一個字節是圖片的類型
),該方法必須要下載完圖片才能確定.不推薦使用,效率低.
設置圖片控件內容
- 1.給WXTopicItem模型添加是否為動態圖
is_gif
屬性,額外添加是否為長圖bigPicture
屬性. - 2.在模型cellHeight屬性的set方法中,計算中間圖片控件高度的地方判斷圖片是否是長圖.
// 超過屏幕高度的圖片為長圖
if (centerH >= XMGScreenH) {
centerH = 200;
self.bigPicture = YES;
}
3.圖片控件WXTopicPictureView對外提供模型數據
4.在WXTopicCell.m文件的模型set方法setTopicItem:中,
控制pictureView的隱藏/顯示處
,為pictureView提供模型數據self.pictureView.topicItem = topicItem;
.-
5.重寫模型數據的set方法,設置pictureView的數據
- 設置圖片控件顯示的圖片
- 控制左上角gif圖片隱藏/顯示.
- 控制點擊顯示大圖按鈕的隱藏/顯示,并控制長圖與普通圖的imageView的內容模式contentMode和clipsToBounds.
- 圖片空間高度變小,內容模式改變為Top模式,超出部分裁減.
- 點擊顯示大圖處理
- 圖片控件模型數據的set方法實現參考代碼
- (void)setTopicItem:(WXTopicItem *)topicItem { _topicItem = topicItem; // 設置圖片 [self.imageView wx_setLargetImage:topicItem.image1 smallImage:topicItem.image0 placeholder:nil]; // 控制gif圖片隱藏/顯示 self.gifView.hidden = !topicItem.is_gif; // 顯示長圖按鈕 if (topicItem.isBigPicture) { self.seeBigPictureButton.hidden = NO; self.imageView.contentMode = UIViewContentModeTop; self.imageView.clipsToBounds = YES; } else { self.seeBigPictureButton.hidden = YES; self.imageView.contentMode = UIViewContentModeScaleToFill; self.imageView.clipsToBounds = NO; } }
- 聲音,視頻,圖片帖子運行效果
聲音,視頻,圖片類型帖子運行效果.gif
存在問題
- 滾動會有卡頓
- 4s真機測試頻繁出現內存警告
補充
xib命名時需注意
-
xib命名需注意,避免控制器加載的時候加載該xib.
- 使用[[WXPictureViewController alloc] init]方法創建控制器是會自動加載
WXPictureViewController.xib
,WXPictureView.xib
.所以使用xib自定義view時,要注意命名,避免控制器加載的時候加載到該xib的view.
- 使用[[WXPictureViewController alloc] init]方法創建控制器是會自動加載
-
加載xib錯誤信息
- 1.錯誤信息
-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "WXPictureView" nib but the view outlet was not set. 錯誤原因:通過加載WXPictureView.xib來創建控制器的view時,并沒有在WXPictureView.xib中明確指出控制器的view是誰. // 1> 沒有設置File's Owner為控制器 // 2> 沒有設置控制器的view屬性
- 2.錯誤信息
-[UITableViewController loadView] loaded the "WXPictureView" nib but didn't get a UITableView. 錯誤原因:通過加載WXPictureView.xib來創建控制器的view時,沒有在WXPictureView.xib中設置控制器的view是tableView(因為當前控制器是一個UITableViewController)
SDWebImage補充
- SDWebImage是
使用url作為key
來存儲已下載的圖片.