第五天任務
今天主要完成精華頁面中cell內內容的處理。
- cell高度的計算
- cell中間內容的顯示
- 精華模塊的重構
- 查看圖片
- 保存圖片到相冊
cell高度的計算
cell間距的設置,每個cell之間有10的間距,因為cell的重用機制,我們發現即使在tableView :didDeselectRowAtIndexPath
方法中通過點擊cell,減少cell的高度,當cell重新顯示的時候還是會變回原來的高度,并且系統內部對cell進行了一些處理,已經在內部設置好cell的frame,所以我們通過重寫cell的setFrame方法對系統設置cell的frame進行攔截,我們先做一些處理,然后在讓系統設置。
//重寫這個方法的目的: 能夠攔截所有設置cell frame的操作
- (void)setFrame:(CGRect)frame
{
// 先設置cell的高度減10,然后在讓系統內部設置。
frame.size.height -= XMGMargin;
[super setFrame:frame];
}
cell高度的計算
cell的高度計算需要根據每一個cell的不同內容進行計算,模型中添加type屬性,用來區別cell中間內容。這里使用枚舉。
typedef NS_ENUM(NSInteger , CLTopicType) {
/** 全部 */
CLTopicTypeAll = 1,
/** 圖片 */
CLTopicTypePicture = 10,
/** 段子 */
CLTopicTypeWord = 29,
/** 音頻 */
CLTopicTypeVoice = 31,
/** 視頻 */
CLTopicTypeVideo = 41,
};
/** 中間內容類型 */
@property(nonatomic,assign)CLTopicType type;
另外我們可以將cell高度計算分為五部分,看下圖
而cell的內容,文字,圖片高度等只能在模型中拿到,所以在模型中添加cellHeight屬性和contentF屬性,重寫cellHeight的get方法計算cell的高度。并且將計算好中間內容的fram用contentF存儲起來,用來之后在cell中設置中間內容的frame。
計算高度的代碼,其中需要注意的地方都已經寫了注釋。
// cell高度的計算
-(CGFloat)cellHeight
{
// iOS8 開始cell不會緩存 cell的高度,每次顯示cell都會來到這里計算一下,造成不必要計算
// 如果計算過一次高度就不要在計算了,直接返回模型的高度即可。
// if (_cellHeight)return _cellHeight;
if (_cellHeight == 0) {
// 1.頭像高度
_cellHeight = 56;
// 2.文字高度 需要提供文字的大小和顯示的寬度
CGFloat textMaxW = [UIScreen mainScreen].bounds.size.width - 2 * CLMargin;
// 需要給一個較大值的高度,如果計算出來的高度小于這個高度就會使用計算出來的高度
CGSize textMaxSize = CGSizeMake(textMaxW, MAXFLOAT);
// CGSize textSize = [self.text sizeWithFont:[UIFont systemFontOfSize:15] constrainedToSize:textMaxSize];
CGSize textSize = [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size;
_cellHeight += textSize.height + CLMargin;
// 3. 圖片的高度,需要判斷有沒有圖片顯示
if (self.type != CLTopicTypeWord) {
// 圖片高度需要根據能顯示的最大寬度等比進行計算 中間內容高度 = 中間內容寬度 * 圖片實際高度 / 圖片實際寬度
CGFloat Height = textMaxW * self.height / self.width;
// 判斷是否是大圖,如果是大圖則高度設置為250
if (Height >= [UIScreen mainScreen].bounds.size.height) {
Height = 250;
self.isBigPicture = YES;
}
self.contentF = CGRectMake(CLMargin, _cellHeight, textMaxW, Height);
_cellHeight += Height + CLMargin;
}
// 4. 最熱評論高度計算
if (self.top_cmt) {
// 4.1 最熱評論標題高度 18
_cellHeight += 18;
// 如果最熱評論是音頻
NSString *contentText = topic.top_cmt.content;
// 如果音頻url有長度,說明是語音評論,需要將高度計算其中,防止用戶名過長,熱門評論高度計算不對
if (topic.top_cmt.voiceuri.length) {
contentText = @"[語音消息]";
}
// 4.2 最熱評論內容高度
NSString *topCmtContent = [NSString stringWithFormat:@"%@ : %@",self.top_cmt.user.username,contentText];
// CGSize topCmtContentSize = [topCmtContent sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:textMaxSize];
CGSize topCmtContentSize = [topCmtContent boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size;
_cellHeight += topCmtContentSize.height + CLMargin;
}
// 5. 底部工具條 + cell之間的間距10
_cellHeight += 35 + CLMargin;
}
return _cellHeight;
}
最后在tableView: heightForRowAtIndexPath:
中拿到模型直接返回cellHeight即可
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 直接返回cell的高度即可
return self.topicArr[indexPath.row].cellHeight;
}
至此cell的高度已經根據每個cell顯示內容不同而決定,在這里需要強調一個問題:cell的高度沒有必要再每次顯示的時候都重新計算一遍,所以先對cellHeight進行判斷,如果有值則直接返回即可,沒有值在進行計算,避免不必要且耗時的計算。
cell中間內容的顯示
cell中間內容分為四大模塊,視頻、音頻、圖片、段子。段子沒有圖片顯示,我們使用xib來分別描述視頻,音頻,和圖片的顯示。如圖
這里圖片里面又分為普通圖片、gif圖片、長圖。需要根據圖片的不同判斷gif標識ImageView和點擊查看大圖Button是否隱藏。
因為之前在計算cell高度的時候使用模型中屬性contentF存儲了中間內容的frame,在CLTopicCell中的setTopic:方法中通過判斷中間內容的類型,決定顯示的內容
#pragma mark - 中間數據類型
if (topic.type == CLTopicTypeVideo) {
self.videoView.hidden = NO;
self.videoView.frame = topic.contentF;
self.videoView.topic = topic;
self.voiceView.hidden = YES;
self.pictureView.hidden = YES;
}else if (topic.type == CLTopicTypeVoice){
self.videoView.hidden = YES;
self.voiceView.hidden = NO;
self.voiceView.frame = topic.contentF;
self.voiceView.topic = topic;
self.pictureView.hidden = YES;
}else if (topic.type == CLTopicTypeWord){
self.videoView.hidden = YES;
self.voiceView.hidden = YES;
self.pictureView.hidden = YES;
}else if (topic.type == CLTopicTypePicture){
self.videoView.hidden = YES;
self.voiceView.hidden = YES;
self.pictureView.hidden = NO;
self.pictureView.frame = topic.contentF;
self.pictureView.topic = topic;
}
注意:因為cell的重用機制,所以中間內容為video的cell很有可能重用到其他內容的cell中,所以需要顯示自己內容的同時,隱藏其他兩種內容的view,防止發生錯亂,其中段子cell中沒有圖片顯示,需要將其他三種cell的view全部隱藏。
另外:在這里根據模型中存儲的中間內容的frame設置中間內容view的frame,此時發現,雖然我們計算好的中間內容的frame是正確的,但是顯示在cell中的frame,只有x,y值正確,width和height不正確。
這是因為在xib中使用了自動布局,從xib中加載進來的控件的autoresizingMask默認是UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight ,會自動將控件根據父控件進行伸縮,所以造成了width和height不正確。只需要在-(void)awakeFromNib
方法中取消其伸縮效果即可self.autoresizingMask = UIViewAutoresizingNone;
中間內容圖片的顯示
中間內容的圖片url可以通過模型拿到,所以給三種類型的View添加模型屬性,并在cell中根據類型設置view顯示的時候,將模型賦值給view的模型屬性,拿到模型屬性即可拿到中間圖片的url。視頻和音頻服務器也提供一張圖片供顯示,根據服務器返回得圖片url賦值給iamgeView即可。
圖片的設置稍有些復雜,數據庫返回給我們三種圖片,小圖,中圖和原圖,我們這里先使用原圖。在View的setTopic方法中設置imageView的圖片即可。
其中圖片需要添加判斷是否為gif圖片和是否為長圖。
// 判斷是否為gif
if (topic.is_gif) {
self.gifImageView.hidden = NO;
}else{
self.gifImageView.hidden = YES;
}
// 判斷是否為大圖
if(topic.isBigPicture){
self.seeBigButton.hidden = NO;
// 設置imageView的裁剪,以顯示頂部為準
self.imageView.contentMode = UIViewContentModeTop;
self.imageView.clipsToBounds = YES;
}else{
self.seeBigButton.hidden = YES;
// 如果不是需要將大圖的設置還原,防止cell重用設置
self.imageView.contentMode = UIViewContentModeScaleToFill;
self.imageView.clipsToBounds = NO;
}
其中判斷gif服務器提供了是否為gif的屬性,直接判斷即可,判斷是否為大圖,需要我們自己添加isBigPicture屬性,并且回到設置cell高度的方法,如果中間內容的高度超過一個屏幕高度,則表示是長圖,設置isBigPicture為YES。并且同樣需要注意cell重用的問題,設置顯示gif標識和查看大圖button顯示就需要在相對的方法中設置隱藏,防止cell重用時發生錯亂。
中間內容顯示的一些細節處理
此時中間內容已經可以顯示,但是還是需要做一些細節處理。
-
長圖顯示的處理,此時我們看到的長圖的顯示是這樣的
未處理長圖顯示
圖片被壓縮填充在ImageView中,此時在判斷如果是長圖的方法中修改imageView的contentMode即可
// 設置imageView的內容以頂端對齊顯示,多余的會被裁剪掉
self.imageView.contentMode = UIViewContentModeTop;
同樣,防止cell重用發生錯亂如果不是長圖需要設置
self.imageView.contentMode = UIViewContentModeScaleToFill;
- 圖片顯示進度條,進度條使用的DACircularProgress第三方。方法非常簡單,這里不在贅述。可以使用sd的方法監聽下載進度。
// 設置圖片并顯示進度
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]placeholderImage:nil options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// receivedSize :已經下載的進度
// expectedSize :完整大小
CGFloat progress =1.0 * receivedSize / expectedSize;
self.progressView.progress = progress;
self.progressView.progressLabel.text = [NSString stringWithFormat:@"%.0f%%",progress * 100];
self.progressView.hidden = NO;
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
self.progressView.hidden = YES;
}];
- 視頻和音頻view播放次數,播放次數的顯示非常簡單,并且視頻和音頻一樣,只不過修改一下控件即可
-(void)setTopic:(CLTopic *)topic
{
_topic = topic;
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]];
NSInteger minute = topic.videotime / 60;
NSInteger second = topic.videotime % 60;
self.videoTimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd",minute , second];
self.playCountLabel.text = [NSString stringWithFormat:@"%zd次播放",topic.playcount];
}
- 監控網絡狀態對顯示圖片進行簡單優化
前面提到過服務器返回給我們的圖片數據有三種小圖,中圖,大圖,我們可以使用AFN對用戶當前網絡進行判斷,如果當前用戶使用的是蜂窩網絡,則加載小圖,為用戶節省流量,同時也加快cell中圖片顯示的速度。如果用戶在wifi環境下,則加載原圖,提高圖片清晰度。如果用戶當前沒有網絡,則提醒用戶沒有網絡。
//使用AFN進行網絡判斷
AFNetworkReachabilityStatus status = [AFNetworkReachabilityManager sharedManager].networkReachabilityStatus;
if (status == AFNetworkReachabilityStatusReachableViaWWAN) { // 手機自帶網絡
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.small_image]];
} else if (status == AFNetworkReachabilityStatusReachableViaWiFi) { // WIFI
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]];
} else { // 網絡有問題, 清空當前顯示的圖片
self.imageView.image = nil;
}
注意:模擬器無法區分網絡狀態,需要真機測試。
精華模塊的重構
全部界面完成之后,我們發現之后的視頻,音頻,圖片,段子的頁面顯示非常簡單,直接將全部界面的代碼復制過去,修改數據請求的參數即可,1為全部,41為視頻,31為音頻,10為圖片,29為段子。但是這樣一來,造成了大量的重復代碼,精華控制器的5個子控制器內代碼基本相同,此時可以使用繼承來重構代碼。
創建基類CLTopicViewController繼承自UITableViewController,其他五個子類繼承CLTopicViewController,同樣將代碼復制過來。
重構的方法很多種,我們通過比較選擇最好的一種。
- 通過判斷控制器的類型,根據不同控制器類型不同,確定不同的請求參數
if ([NSStringFromClass(self.class) isEqualToString:@"CLAllViewController"]) {
params[@"type"] = @"1";
} else if ([NSStringFromClass(self.class) isEqualToString:@"CLVideoViewController"]) {
params[@"type"] = @"41";
} else if ([NSStringFromClass(self.class) isEqualToString:@"CLVoiceViewController"]) {
params[@"type"] = @"31";
} else if ([NSStringFromClass(self.class) isEqualToString:@"CLPictureViewController"]) {
params[@"type"] = @"10";
} else if ([NSStringFromClass(self.class) isEqualToString:@"CLWordViewController"]) {
params[@"type"] = @"29";
}
缺點:需要些大量繁瑣代碼,下拉刷新和上拉加載都需要重新判斷一遍,并且這里由父控制器來設置子控制器的type,違背了誰的內容由其自己管理的代碼原則。
- 給基類添加一個type屬性
/** 帖子的類型 */
// @property (nonatomic, assign) CLTopicType type;
然后我們在給主控制器添加子控制器的時候就可以設置子控制器的 type屬性,舉一個例子,其他子控制器相同。
CLAllViewController *all = [[CLAllViewController alloc]init];
all.type = CLTopicTypeAll;
[self addChildViewController:all];
其實此時我們直接使用基類即可,主控制器中添加5個基類控制器,每個基類控制器的type屬性不同,但是這樣做很有局限性,如果之后有需求需要往子控制器中添加單獨的控件,或者個性化設置,還是需要在基類中進行判斷,延展性非常不好。
- 通過重寫基類type屬性的get方法
基類中提供type的get方法,我們可以在子類中重寫基類的get方法,返回type,get方法只能子類可以重寫,其他類也沒有辦法改變子類的type。保證了父類中的某個內容, 只允許由子類來修改或提供, 不能由外界來修改或提供,并且我們可以在子類中對子類單獨的界面做一些個性化的設置,延展性非常好。
- (CLTopicType)type
{
return CLTopicTypeAll;
}
其實也可以為屬性添加readonly,這個屬性會生成一個type的get方法和 _type成員變量,相較于上面的方法,多創建了沒有必要的成員變量。并且需要考慮代碼順序問題,如果在父類中對type屬性有一些調用,則會出現問題,因為type在super方法之后設置。
至此我們通過繼承并重寫type的get方法對精華模塊進行了重構。子控制器內的代碼變得非常簡單,只需要重寫覆蓋父類的get方法即可,并且可以在子類中對子類進行一些個性化的設置。
查看圖片
對于圖片cell,點擊圖片會Mode出一個控制器來顯示圖片,同樣使用xib來描述圖片顯示控制器,創建CLSeeBigViewController控制器,通過xib描述控制器view
在圖片的view,CLTopicPictureView中為中間顯示圖片的iamgeView添加點擊事件,imageView默認不支持交互,需要開啟交互。
self.imageView.userInteractionEnabled = YES;
[self.imageView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(seeBig)]];
- (void)seeBig
{
CLSeeBigViewController *seeBig = [[CLSeeBigViewController alloc] init];
seeBig.topic = self.topic;
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:seeBig animated:YES completion:nil];
}
接下來需要在CLSeeBigViewController中進行一些判斷,首先有可能是長圖,長圖的長度肯定超過一個屏幕大小,所以CLSeeBigViewController中需要使用scrollView來顯示長圖,因為xib中已經在CLSeeBigViewController的view上添加了返回和保存按鈕,所以scrollView需要使用insertSubview:atIndex
添加在最底層,防止后加入的scrollView覆蓋擋住返回和保存按鈕。并在scrollView中添加imageView。
對圖片的長度進行計算,如果長度沒有超過一個屏幕大小,則根據屏幕的寬高比計算出圖片的高度,居中顯示在屏幕中,保證imageView占據整個屏幕的寬度。如果長度超過一個屏幕大小,則設置imageView的y值為0,scrollView的contentSize橫向為0,縱向為圖片的高度。
最后通過scrollView的代理方法對imageView的縮放比例進行設置。
#import "CLSeeBigViewController.h"
#import "CLTopic.h"
#import <UIImageView+WebCache.h>
@interface CLSeeBigViewController ()<UIScrollViewDelegate>
/** 圖片控件 */
@property (nonatomic, weak) UIImageView *imageView;
@end
@implementation CLSeeBigViewController
- (void)viewDidLoad {
[super viewDidLoad];
// scrollView
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.delegate = self;
scrollView.frame = [UIScreen mainScreen].bounds;
[self.view insertSubview:scrollView atIndex:0];
// imageView
UIImageView *imageView = [[UIImageView alloc] init];
[imageView sd_setImageWithURL:[NSURL URLWithString:self.topic.large_image]];
[scrollView addSubview:imageView];
imageView.cl_width = scrollView.cl_width;
imageView.cl_height = self.topic.height * imageView.cl_width / self.topic.width;
imageView.cl_x = 0;
if (imageView.cl_height >= scrollView.cl_height) { // 圖片高度超過整個屏幕
imageView.cl_y = 0;
// 滾動范圍
scrollView.contentSize = CGSizeMake(0, imageView.cl_height);
} else { // 居中顯示
imageView.cl_centerY = scrollView.cl_height * 0.5;
}
self.imageView = imageView;
// 縮放比例
CGFloat scale = self.topic.width / imageView.cl_width;
if (scale > 1.0) {
scrollView.maximumZoomScale = scale;
}
}
- (IBAction)back {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)save {
}
#pragma mark - <UIScrollViewDelegate>
//返回一個scrollView的子控件進行縮放
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
@end
保存圖片到相冊
保存圖片到相冊需要用到的框架
#import <AssetsLibrary/AssetsLibrary.h> // iOS9開始廢棄
#import <Photos/Photos.h> // iOS9開始推薦
首先來看一下系統相簿的內容
如果僅僅是將圖片保存到系統中相機膠卷相簿中,<AssetsLibrary/AssetsLibrary.h>
提供了非常簡單的函數。
UIImageWriteToSavedPhotosAlbum(self.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
這個函數中的SEL方法必須按照一定格式傳三個參數才可以,方法內部已經給出說明
訪問系統相冊需要獲得用戶授權,且只會請求一次,如果用戶點擊了不允許,則永遠不允許訪問相冊,此時需要提醒用戶去[設置]-[隱私]-[照片]中開啟。
我們這里想要實現將圖片保存到項目自己創建的相簿中,其實將圖片保存到項目自己創建的相簿中,也需要先將圖片保存到相機膠卷相簿中,然后在轉移到自己創建的相簿中。
將圖片保存到自己創建相簿的步驟
1.判斷用戶授權情況
// 獲取用戶授權狀態
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
// 授權狀態
PHAuthorizationStatusRestricted, 因為家長控制, 導致應用無法方法相冊(跟用戶的選擇沒有關系)很少出現。
如果這種狀態提醒用戶 系統原因無法訪問相冊
PHAuthorizationStatusDenied, 用戶拒絕當前應用訪問相冊(用戶當初點擊了"不允許")
如果用戶拒絕,提醒用戶去[設置]-[隱私]-[照片]中開啟。
PHAuthorizationStatusAuthorized,用戶允許當前應用訪問相冊(用戶當初點擊了"好")
如果獲得用戶授權,則開始保存圖片
PHAuthorizationStatusNotDetermined, 用戶還沒有做出選擇
如果用戶還沒有做出選擇,則對用戶授權信息進行請求,如果用戶點擊了不允許則什么都不做,點擊了好則開始保存圖片
2.將圖片存儲在交卷相冊中
3.判斷是否已經創建自己相簿
4.如果已經創建了則獲得曾經創建過的相簿,獲得圖片,獲取添加圖片到相簿中的請求,將圖片添加到相簿
5.如果沒有創建相簿,創建相簿的請求,獲得創建相簿,獲得圖片,獲取圖片添加到相簿的請求,將圖片添加到相簿中
直接來看保存圖片到相冊的save按鈕點擊事件吧,<Photos/Photos.h>
框架的設計雖然使用起來繁瑣,但是非常巧妙,如果想對"相冊"進行修改(增刪改), 那么修改代碼必須放在[PHPhotoLibrary sharedPhotoLibrary]
的performChanges
方法的block中,并且將圖片添加到相簿中、創建相簿都是耗時操作,他們都在子線程中執行。所以如果做添加過程中想要修改UI,例如提醒用戶保存成功或失敗等,需要會到主線程中執行。
- (IBAction)save {
/*
PHAuthorizationStatusNotDetermined, 用戶還沒有做出選擇
PHAuthorizationStatusDenied, 用戶拒絕當前應用訪問相冊(用戶當初點擊了"不允許")
PHAuthorizationStatusAuthorized 用戶允許當前應用訪問相冊(用戶當初點擊了"好")
PHAuthorizationStatusRestricted, 因為家長控制, 導致應用無法方法相冊(跟用戶的選擇沒有關系)
*/
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusRestricted) {
// 因為家長控制,導致應用無法訪問相冊(與用戶沒有關系)
[SVProgressHUD showErrorWithStatus:@"因為系統原因,無法訪問系統相冊"];
}else if (status == PHAuthorizationStatusDenied){
// 用戶點擊了不允許
CLLog(@"設置-隱私-照片-百思不得姐xx_cc-允許");
}else if (status == PHAuthorizationStatusAuthorized){
// 獲得用戶授權,在這里保存圖片
[self saveImage];
}else if (status == PHAuthorizationStatusNotDetermined){
// 用戶還沒有選擇進行授權
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
// 用戶點擊好或者不允許 都會到這里,如果不允許則什么都不做,如果好,則保存圖片
if (status == PHAuthorizationStatusAuthorized) {
// 保存圖片
[self saveImage];
}
}];
}
}
/** 保存圖片到相冊 */
- (void)saveImage
{
// PHAsset : 一個資源, 比如一張圖片\一段視頻
// PHAssetCollection : 一個相簿
// PHAsset的標識, 利用這個標識可以找到對應的PHAsset對象(圖片對象)
__block NSString *assetLocalIdentifier = nil;
// 如果想對"相冊"進行修改(增刪改), 那么修改代碼必須放在[PHPhotoLibrary sharedPhotoLibrary]的performChanges方法的block中
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 1.保存圖片A到"相機膠卷"中
// 創建圖片的請求
assetLocalIdentifier = [PHAssetCreationRequest creationRequestForAssetFromImage:self.imageView.image].placeholderForCreatedAsset.localIdentifier;
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// 這個方法在子線程中執行,所以需要返回到主線程中去修改UI
if (success == NO) {
[self showError:@"保存圖片失敗!"];
return;
}
// 2.獲得相簿
PHAssetCollection *createdAssetCollection = [self createdAssetCollection];
if (createdAssetCollection == nil) {
// 這個方法在子線程中執行,所以需要返回到主線程中去修改UI
[self showError:@"創建相簿失敗!"];
return;
}
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 3.添加"相機膠卷"中的圖片A到"相簿"D中
// 獲得圖片
PHAsset *asset = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetLocalIdentifier] options:nil].lastObject;
// 添加圖片到相簿中的請求
PHAssetCollectionChangeRequest *request = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:createdAssetCollection];
// 添加圖片到相簿
[request addAssets:@[asset]];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// 這個方法在子線程中執行,所以需要返回到主線程中去修改UI
if (success == NO) {
[self showError:@"保存圖片失敗!"];;
} else {
[self showSuccess:@"保存圖片成功!"];;
}
}];
}];
}
/**
* 獲得相簿
* 如果已經找到應用對應的相簿則直接添加到相簿,如果沒有找到則創建新的相簿
*/
- (PHAssetCollection *)createdAssetCollection
{
// 相簿名字 CLAssetCollectionTitle
static NSString * CLAssetCollectionTitle = @"百思不得姐xx_cc";
// 從已存在相簿中查找這個應用對應的相簿
PHFetchResult<PHAssetCollection *> *assetCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (PHAssetCollection *assetCollection in assetCollections) {
if ([assetCollection.localizedTitle isEqualToString:CLAssetCollectionTitle]) {
return assetCollection;
}
}
// 沒有找到對應的相簿, 得創建新的相簿
// 錯誤信息
NSError *error = nil;
// PHAssetCollection的標識, 利用這個標識可以找到對應的PHAssetCollection對象(相簿對象)
__block NSString *assetCollectionLocalIdentifier = nil;
// 這個方法在主線程張中執行,等相簿創建完畢之后才會返回
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
// 創建相簿的請求 CLAssetCollectionTitle 表示相簿名字
assetCollectionLocalIdentifier = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:CLAssetCollectionTitle].placeholderForCreatedAssetCollection.localIdentifier;
} error:&error];
// 如果有錯誤信息
if (error) return nil;
// 獲得剛才創建的相簿
return [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[assetCollectionLocalIdentifier] options:nil].lastObject;
}
// 注意:因為添加圖片到相簿,和創建相簿都在子線程中執行,所以修改UI需要回到主線程,我們這里進行了封裝。
- (void)showSuccess:(NSString *)text
{
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:text];
});
}
- (void)showError:(NSString *)text
{
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:text];
});
}
雖然將圖片保存到自己創建的項目相簿中比較繁瑣,但是寫一次基本上就可以循環利用了,上面的代碼稍作修改完全可以使用到別的項目中。
總結
今天主要完成了cell內部的一些細節操作,計算cell的高度,顯示cell內容,查看圖片等等,同時對精華模塊進行重構,使得精華模塊的結構更加清晰。
看一下第五天成果
文中如果有不對的地方歡迎指出。我是xx_cc,一只長大很久但還沒有二夠的家伙。