第五天任務(wù)
今天主要完成精華頁(yè)面中cell內(nèi)內(nèi)容的處理漆枚。
- cell高度的計(jì)算
- cell中間內(nèi)容的顯示
- 精華模塊的重構(gòu)
- 查看圖片
- 保存圖片到相冊(cè)
cell高度的計(jì)算
cell間距的設(shè)置,每個(gè)cell之間有10的間距瘫筐,因?yàn)閏ell的重用機(jī)制堤魁,我們發(fā)現(xiàn)即使在tableView :didDeselectRowAtIndexPath
方法中通過(guò)點(diǎn)擊cell比吭,減少cell的高度,當(dāng)cell重新顯示的時(shí)候還是會(huì)變回原來(lái)的高度姨涡,并且系統(tǒng)內(nèi)部對(duì)cell進(jìn)行了一些處理衩藤,已經(jīng)在內(nèi)部設(shè)置好cell的frame,所以我們通過(guò)重寫cell的setFrame方法對(duì)系統(tǒng)設(shè)置cell的frame進(jìn)行攔截涛漂,我們先做一些處理赏表,然后在讓系統(tǒng)設(shè)置。
//重寫這個(gè)方法的目的: 能夠攔截所有設(shè)置cell frame的操作
- (void)setFrame:(CGRect)frame
{
// 先設(shè)置cell的高度減10匈仗,然后在讓系統(tǒng)內(nèi)部設(shè)置瓢剿。
frame.size.height -= XMGMargin;
[super setFrame:frame];
}
cell高度的計(jì)算
cell的高度計(jì)算需要根據(jù)每一個(gè)cell的不同內(nèi)容進(jìn)行計(jì)算,模型中添加type屬性悠轩,用來(lái)區(qū)別cell中間內(nèi)容间狂。這里使用枚舉。
typedef NS_ENUM(NSInteger , CLTopicType) {
/** 全部 */
CLTopicTypeAll = 1,
/** 圖片 */
CLTopicTypePicture = 10,
/** 段子 */
CLTopicTypeWord = 29,
/** 音頻 */
CLTopicTypeVoice = 31,
/** 視頻 */
CLTopicTypeVideo = 41,
};
/** 中間內(nèi)容類型 */
@property(nonatomic,assign)CLTopicType type;
另外我們可以將cell高度計(jì)算分為五部分火架,看下圖
而cell的內(nèi)容鉴象,文字忙菠,圖片高度等只能在模型中拿到,所以在模型中添加cellHeight屬性和contentF屬性纺弊,重寫cellHeight的get方法計(jì)算cell的高度牛欢。并且將計(jì)算好中間內(nèi)容的fram用contentF存儲(chǔ)起來(lái),用來(lái)之后在cell中設(shè)置中間內(nèi)容的frame淆游。
計(jì)算高度的代碼傍睹,其中需要注意的地方都已經(jīng)寫了注釋。
// cell高度的計(jì)算
-(CGFloat)cellHeight
{
// iOS8 開(kāi)始cell不會(huì)緩存 cell的高度犹菱,每次顯示cell都會(huì)來(lái)到這里計(jì)算一下拾稳,造成不必要計(jì)算
// 如果計(jì)算過(guò)一次高度就不要在計(jì)算了,直接返回模型的高度即可。
// if (_cellHeight)return _cellHeight;
if (_cellHeight == 0) {
// 1.頭像高度
_cellHeight = 56;
// 2.文字高度 需要提供文字的大小和顯示的寬度
CGFloat textMaxW = [UIScreen mainScreen].bounds.size.width - 2 * CLMargin;
// 需要給一個(gè)較大值的高度腊脱,如果計(jì)算出來(lái)的高度小于這個(gè)高度就會(huì)使用計(jì)算出來(lái)的高度
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. 圖片的高度访得,需要判斷有沒(méi)有圖片顯示
if (self.type != CLTopicTypeWord) {
// 圖片高度需要根據(jù)能顯示的最大寬度等比進(jìn)行計(jì)算 中間內(nèi)容高度 = 中間內(nèi)容寬度 * 圖片實(shí)際高度 / 圖片實(shí)際寬度
CGFloat Height = textMaxW * self.height / self.width;
// 判斷是否是大圖,如果是大圖則高度設(shè)置為250
if (Height >= [UIScreen mainScreen].bounds.size.height) {
Height = 250;
self.isBigPicture = YES;
}
self.contentF = CGRectMake(CLMargin, _cellHeight, textMaxW, Height);
_cellHeight += Height + CLMargin;
}
// 4. 最熱評(píng)論高度計(jì)算
if (self.top_cmt) {
// 4.1 最熱評(píng)論標(biāo)題高度 18
_cellHeight += 18;
// 如果最熱評(píng)論是音頻
NSString *contentText = topic.top_cmt.content;
// 如果音頻url有長(zhǎng)度虑椎,說(shuō)明是語(yǔ)音評(píng)論,需要將高度計(jì)算其中俱笛,防止用戶名過(guò)長(zhǎng)捆姜,熱門評(píng)論高度計(jì)算不對(duì)
if (topic.top_cmt.voiceuri.length) {
contentText = @"[語(yǔ)音消息]";
}
// 4.2 最熱評(píng)論內(nèi)容高度
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的高度已經(jīng)根據(jù)每個(gè)cell顯示內(nèi)容不同而決定,在這里需要強(qiáng)調(diào)一個(gè)問(wèn)題:cell的高度沒(méi)有必要再每次顯示的時(shí)候都重新計(jì)算一遍迎膜,所以先對(duì)cellHeight進(jìn)行判斷泥技,如果有值則直接返回即可,沒(méi)有值在進(jìn)行計(jì)算磕仅,避免不必要且耗時(shí)的計(jì)算珊豹。
cell中間內(nèi)容的顯示
cell中間內(nèi)容分為四大模塊,視頻榕订、音頻店茶、圖片、段子劫恒。段子沒(méi)有圖片顯示贩幻,我們使用xib來(lái)分別描述視頻,音頻两嘴,和圖片的顯示丛楚。如圖
這里圖片里面又分為普通圖片、gif圖片憔辫、長(zhǎng)圖趣些。需要根據(jù)圖片的不同判斷gif標(biāo)識(shí)ImageView和點(diǎn)擊查看大圖Button是否隱藏。
因?yàn)橹霸谟?jì)算cell高度的時(shí)候使用模型中屬性contentF存儲(chǔ)了中間內(nèi)容的frame贰您,在CLTopicCell中的setTopic:方法中通過(guò)判斷中間內(nèi)容的類型坏平,決定顯示的內(nèi)容
#pragma mark - 中間數(shù)據(jù)類型
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;
}
注意:因?yàn)閏ell的重用機(jī)制拢操,所以中間內(nèi)容為video的cell很有可能重用到其他內(nèi)容的cell中,所以需要顯示自己內(nèi)容的同時(shí)功茴,隱藏其他兩種內(nèi)容的view庐冯,防止發(fā)生錯(cuò)亂,其中段子cell中沒(méi)有圖片顯示坎穿,需要將其他三種cell的view全部隱藏展父。
另外:在這里根據(jù)模型中存儲(chǔ)的中間內(nèi)容的frame設(shè)置中間內(nèi)容view的frame,此時(shí)發(fā)現(xiàn)玲昧,雖然我們計(jì)算好的中間內(nèi)容的frame是正確的栖茉,但是顯示在cell中的frame,只有x孵延,y值正確吕漂,width和height不正確。
這是因?yàn)樵趚ib中使用了自動(dòng)布局尘应,從xib中加載進(jìn)來(lái)的控件的autoresizingMask默認(rèn)是UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight ,會(huì)自動(dòng)將控件根據(jù)父控件進(jìn)行伸縮惶凝,所以造成了width和height不正確。只需要在-(void)awakeFromNib
方法中取消其伸縮效果即可self.autoresizingMask = UIViewAutoresizingNone;
中間內(nèi)容圖片的顯示
中間內(nèi)容的圖片url可以通過(guò)模型拿到犬钢,所以給三種類型的View添加模型屬性苍鲜,并在cell中根據(jù)類型設(shè)置view顯示的時(shí)候,將模型賦值給view的模型屬性玷犹,拿到模型屬性即可拿到中間圖片的url混滔。視頻和音頻服務(wù)器也提供一張圖片供顯示,根據(jù)服務(wù)器返回得圖片url賦值給iamgeView即可歹颓。
圖片的設(shè)置稍有些復(fù)雜坯屿,數(shù)據(jù)庫(kù)返回給我們?nèi)N圖片,小圖巍扛,中圖和原圖领跛,我們這里先使用原圖。在View的setTopic方法中設(shè)置imageView的圖片即可撤奸。
其中圖片需要添加判斷是否為gif圖片和是否為長(zhǎng)圖隔节。
// 判斷是否為gif
if (topic.is_gif) {
self.gifImageView.hidden = NO;
}else{
self.gifImageView.hidden = YES;
}
// 判斷是否為大圖
if(topic.isBigPicture){
self.seeBigButton.hidden = NO;
// 設(shè)置imageView的裁剪,以顯示頂部為準(zhǔn)
self.imageView.contentMode = UIViewContentModeTop;
self.imageView.clipsToBounds = YES;
}else{
self.seeBigButton.hidden = YES;
// 如果不是需要將大圖的設(shè)置還原寂呛,防止cell重用設(shè)置
self.imageView.contentMode = UIViewContentModeScaleToFill;
self.imageView.clipsToBounds = NO;
}
其中判斷gif服務(wù)器提供了是否為gif的屬性怎诫,直接判斷即可,判斷是否為大圖贷痪,需要我們自己添加isBigPicture屬性幻妓,并且回到設(shè)置cell高度的方法,如果中間內(nèi)容的高度超過(guò)一個(gè)屏幕高度,則表示是長(zhǎng)圖肉津,設(shè)置isBigPicture為YES强胰。并且同樣需要注意cell重用的問(wèn)題,設(shè)置顯示gif標(biāo)識(shí)和查看大圖button顯示就需要在相對(duì)的方法中設(shè)置隱藏妹沙,防止cell重用時(shí)發(fā)生錯(cuò)亂偶洋。
中間內(nèi)容顯示的一些細(xì)節(jié)處理
此時(shí)中間內(nèi)容已經(jīng)可以顯示,但是還是需要做一些細(xì)節(jié)處理距糖。
-
長(zhǎng)圖顯示的處理玄窝,此時(shí)我們看到的長(zhǎng)圖的顯示是這樣的
圖片被壓縮填充在ImageView中,此時(shí)在判斷如果是長(zhǎng)圖的方法中修改imageView的contentMode即可
// 設(shè)置imageView的內(nèi)容以頂端對(duì)齊顯示悍引,多余的會(huì)被裁剪掉
self.imageView.contentMode = UIViewContentModeTop;
同樣恩脂,防止cell重用發(fā)生錯(cuò)亂如果不是長(zhǎng)圖需要設(shè)置
self.imageView.contentMode = UIViewContentModeScaleToFill;
- 圖片顯示進(jìn)度條,進(jìn)度條使用的DACircularProgress第三方趣斤。方法非常簡(jiǎn)單俩块,這里不在贅述∨欤可以使用sd的方法監(jiān)聽(tīng)下載進(jìn)度玉凯。
// 設(shè)置圖片并顯示進(jìn)度
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]placeholderImage:nil options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// receivedSize :已經(jīng)下載的進(jìn)度
// 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播放次數(shù),播放次數(shù)的顯示非常簡(jiǎn)單联贩,并且視頻和音頻一樣漫仆,只不過(guò)修改一下控件即可
-(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];
}
- 監(jiān)控網(wǎng)絡(luò)狀態(tài)對(duì)顯示圖片進(jìn)行簡(jiǎn)單優(yōu)化
前面提到過(guò)服務(wù)器返回給我們的圖片數(shù)據(jù)有三種小圖,中圖撑蒜,大圖歹啼,我們可以使用AFN對(duì)用戶當(dāng)前網(wǎng)絡(luò)進(jìn)行判斷玄渗,如果當(dāng)前用戶使用的是蜂窩網(wǎng)絡(luò)座菠,則加載小圖,為用戶節(jié)省流量藤树,同時(shí)也加快cell中圖片顯示的速度浴滴。如果用戶在wifi環(huán)境下,則加載原圖岁钓,提高圖片清晰度升略。如果用戶當(dāng)前沒(méi)有網(wǎng)絡(luò),則提醒用戶沒(méi)有網(wǎng)絡(luò)屡限。
//使用AFN進(jìn)行網(wǎng)絡(luò)判斷
AFNetworkReachabilityStatus status = [AFNetworkReachabilityManager sharedManager].networkReachabilityStatus;
if (status == AFNetworkReachabilityStatusReachableViaWWAN) { // 手機(jī)自帶網(wǎng)絡(luò)
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.small_image]];
} else if (status == AFNetworkReachabilityStatusReachableViaWiFi) { // WIFI
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]];
} else { // 網(wǎng)絡(luò)有問(wèn)題, 清空當(dāng)前顯示的圖片
self.imageView.image = nil;
}
注意:模擬器無(wú)法區(qū)分網(wǎng)絡(luò)狀態(tài)品嚣,需要真機(jī)測(cè)試。
精華模塊的重構(gòu)
全部界面完成之后钧大,我們發(fā)現(xiàn)之后的視頻翰撑,音頻,圖片啊央,段子的頁(yè)面顯示非常簡(jiǎn)單眶诈,直接將全部界面的代碼復(fù)制過(guò)去涨醋,修改數(shù)據(jù)請(qǐng)求的參數(shù)即可,1為全部逝撬,41為視頻浴骂,31為音頻,10為圖片宪潮,29為段子溯警。但是這樣一來(lái),造成了大量的重復(fù)代碼坎炼,精華控制器的5個(gè)子控制器內(nèi)代碼基本相同愧膀,此時(shí)可以使用繼承來(lái)重構(gòu)代碼。
創(chuàng)建基類CLTopicViewController繼承自UITableViewController谣光,其他五個(gè)子類繼承CLTopicViewController檩淋,同樣將代碼復(fù)制過(guò)來(lái)。
重構(gòu)的方法很多種萄金,我們通過(guò)比較選擇最好的一種蟀悦。
- 通過(guò)判斷控制器的類型,根據(jù)不同控制器類型不同氧敢,確定不同的請(qǐng)求參數(shù)
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";
}
缺點(diǎn):需要些大量繁瑣代碼日戈,下拉刷新和上拉加載都需要重新判斷一遍,并且這里由父控制器來(lái)設(shè)置子控制器的type孙乖,違背了誰(shuí)的內(nèi)容由其自己管理的代碼原則浙炼。
- 給基類添加一個(gè)type屬性
/** 帖子的類型 */
// @property (nonatomic, assign) CLTopicType type;
然后我們?cè)诮o主控制器添加子控制器的時(shí)候就可以設(shè)置子控制器的 type屬性,舉一個(gè)例子唯袄,其他子控制器相同弯屈。
CLAllViewController *all = [[CLAllViewController alloc]init];
all.type = CLTopicTypeAll;
[self addChildViewController:all];
其實(shí)此時(shí)我們直接使用基類即可,主控制器中添加5個(gè)基類控制器恋拷,每個(gè)基類控制器的type屬性不同资厉,但是這樣做很有局限性,如果之后有需求需要往子控制器中添加單獨(dú)的控件蔬顾,或者個(gè)性化設(shè)置宴偿,還是需要在基類中進(jìn)行判斷,延展性非常不好诀豁。
- 通過(guò)重寫基類type屬性的get方法
基類中提供type的get方法窄刘,我們可以在子類中重寫基類的get方法,返回type舷胜,get方法只能子類可以重寫娩践,其他類也沒(méi)有辦法改變子類的type。保證了父類中的某個(gè)內(nèi)容, 只允許由子類來(lái)修改或提供, 不能由外界來(lái)修改或提供,并且我們可以在子類中對(duì)子類單獨(dú)的界面做一些個(gè)性化的設(shè)置欺矫,延展性非常好纱新。
- (CLTopicType)type
{
return CLTopicTypeAll;
}
其實(shí)也可以為屬性添加readonly,這個(gè)屬性會(huì)生成一個(gè)type的get方法和 _type成員變量穆趴,相較于上面的方法脸爱,多創(chuàng)建了沒(méi)有必要的成員變量。并且需要考慮代碼順序問(wèn)題未妹,如果在父類中對(duì)type屬性有一些調(diào)用簿废,則會(huì)出現(xiàn)問(wèn)題,因?yàn)閠ype在super方法之后設(shè)置络它。
至此我們通過(guò)繼承并重寫type的get方法對(duì)精華模塊進(jìn)行了重構(gòu)族檬。子控制器內(nèi)的代碼變得非常簡(jiǎn)單,只需要重寫覆蓋父類的get方法即可化戳,并且可以在子類中對(duì)子類進(jìn)行一些個(gè)性化的設(shè)置单料。
查看圖片
對(duì)于圖片cell,點(diǎn)擊圖片會(huì)Mode出一個(gè)控制器來(lái)顯示圖片点楼,同樣使用xib來(lái)描述圖片顯示控制器扫尖,創(chuàng)建CLSeeBigViewController控制器,通過(guò)xib描述控制器view
在圖片的view掠廓,CLTopicPictureView中為中間顯示圖片的iamgeView添加點(diǎn)擊事件换怖,imageView默認(rèn)不支持交互,需要開(kāi)啟交互蟀瞧。
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];
}
接下來(lái)需要在CLSeeBigViewController中進(jìn)行一些判斷沉颂,首先有可能是長(zhǎng)圖,長(zhǎng)圖的長(zhǎng)度肯定超過(guò)一個(gè)屏幕大小悦污,所以CLSeeBigViewController中需要使用scrollView來(lái)顯示長(zhǎng)圖铸屉,因?yàn)閤ib中已經(jīng)在CLSeeBigViewController的view上添加了返回和保存按鈕,所以scrollView需要使用insertSubview:atIndex
添加在最底層塞关,防止后加入的scrollView覆蓋擋住返回和保存按鈕抬探。并在scrollView中添加imageView子巾。
對(duì)圖片的長(zhǎng)度進(jìn)行計(jì)算帆赢,如果長(zhǎng)度沒(méi)有超過(guò)一個(gè)屏幕大小,則根據(jù)屏幕的寬高比計(jì)算出圖片的高度线梗,居中顯示在屏幕中椰于,保證imageView占據(jù)整個(gè)屏幕的寬度。如果長(zhǎng)度超過(guò)一個(gè)屏幕大小仪搔,則設(shè)置imageView的y值為0瘾婿,scrollView的contentSize橫向?yàn)?,縱向?yàn)閳D片的高度。
最后通過(guò)scrollView的代理方法對(duì)imageView的縮放比例進(jìn)行設(shè)置偏陪。
#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) { // 圖片高度超過(guò)整個(gè)屏幕
imageView.cl_y = 0;
// 滾動(dòng)范圍
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>
//返回一個(gè)scrollView的子控件進(jìn)行縮放
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
@end
保存圖片到相冊(cè)
保存圖片到相冊(cè)需要用到的框架
#import <AssetsLibrary/AssetsLibrary.h> // iOS9開(kāi)始廢棄
#import <Photos/Photos.h> // iOS9開(kāi)始推薦
首先來(lái)看一下系統(tǒng)相簿的內(nèi)容
如果僅僅是將圖片保存到系統(tǒng)中相機(jī)膠卷相簿中抢呆,<AssetsLibrary/AssetsLibrary.h>
提供了非常簡(jiǎn)單的函數(shù)。
UIImageWriteToSavedPhotosAlbum(self.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
這個(gè)函數(shù)中的SEL方法必須按照一定格式傳三個(gè)參數(shù)才可以笛谦,方法內(nèi)部已經(jīng)給出說(shuō)明
訪問(wèn)系統(tǒng)相冊(cè)需要獲得用戶授權(quán)抱虐,且只會(huì)請(qǐng)求一次,如果用戶點(diǎn)擊了不允許饥脑,則永遠(yuǎn)不允許訪問(wèn)相冊(cè)恳邀,此時(shí)需要提醒用戶去[設(shè)置]-[隱私]-[照片]中開(kāi)啟。
我們這里想要實(shí)現(xiàn)將圖片保存到項(xiàng)目自己創(chuàng)建的相簿中灶轰,其實(shí)將圖片保存到項(xiàng)目自己創(chuàng)建的相簿中谣沸,也需要先將圖片保存到相機(jī)膠卷相簿中,然后在轉(zhuǎn)移到自己創(chuàng)建的相簿中笋颤。
將圖片保存到自己創(chuàng)建相簿的步驟
1.判斷用戶授權(quán)情況
// 獲取用戶授權(quán)狀態(tài)
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
// 授權(quán)狀態(tài)
PHAuthorizationStatusRestricted, 因?yàn)榧议L(zhǎng)控制, 導(dǎo)致應(yīng)用無(wú)法方法相冊(cè)(跟用戶的選擇沒(méi)有關(guān)系)很少出現(xiàn)乳附。
如果這種狀態(tài)提醒用戶 系統(tǒng)原因無(wú)法訪問(wèn)相冊(cè)
PHAuthorizationStatusDenied, 用戶拒絕當(dāng)前應(yīng)用訪問(wèn)相冊(cè)(用戶當(dāng)初點(diǎn)擊了"不允許")
如果用戶拒絕,提醒用戶去[設(shè)置]-[隱私]-[照片]中開(kāi)啟伴澄。
PHAuthorizationStatusAuthorized许溅,用戶允許當(dāng)前應(yīng)用訪問(wèn)相冊(cè)(用戶當(dāng)初點(diǎn)擊了"好")
如果獲得用戶授權(quán),則開(kāi)始保存圖片
PHAuthorizationStatusNotDetermined, 用戶還沒(méi)有做出選擇
如果用戶還沒(méi)有做出選擇秉版,則對(duì)用戶授權(quán)信息進(jìn)行請(qǐng)求贤重,如果用戶點(diǎn)擊了不允許則什么都不做,點(diǎn)擊了好則開(kāi)始保存圖片
2.將圖片存儲(chǔ)在交卷相冊(cè)中
3.判斷是否已經(jīng)創(chuàng)建自己相簿
4.如果已經(jīng)創(chuàng)建了則獲得曾經(jīng)創(chuàng)建過(guò)的相簿清焕,獲得圖片并蝗,獲取添加圖片到相簿中的請(qǐng)求,將圖片添加到相簿
5.如果沒(méi)有創(chuàng)建相簿秸妥,創(chuàng)建相簿的請(qǐng)求滚停,獲得創(chuàng)建相簿,獲得圖片粥惧,獲取圖片添加到相簿的請(qǐng)求键畴,將圖片添加到相簿中
直接來(lái)看保存圖片到相冊(cè)的save按鈕點(diǎn)擊事件吧,<Photos/Photos.h>
框架的設(shè)計(jì)雖然使用起來(lái)繁瑣突雪,但是非常巧妙起惕,如果想對(duì)"相冊(cè)"進(jìn)行修改(增刪改), 那么修改代碼必須放在[PHPhotoLibrary sharedPhotoLibrary]
的performChanges
方法的block中,并且將圖片添加到相簿中咏删、創(chuàng)建相簿都是耗時(shí)操作惹想,他們都在子線程中執(zhí)行。所以如果做添加過(guò)程中想要修改UI督函,例如提醒用戶保存成功或失敗等嘀粱,需要會(huì)到主線程中執(zhí)行激挪。
- (IBAction)save {
/*
PHAuthorizationStatusNotDetermined, 用戶還沒(méi)有做出選擇
PHAuthorizationStatusDenied, 用戶拒絕當(dāng)前應(yīng)用訪問(wèn)相冊(cè)(用戶當(dāng)初點(diǎn)擊了"不允許")
PHAuthorizationStatusAuthorized 用戶允許當(dāng)前應(yīng)用訪問(wèn)相冊(cè)(用戶當(dāng)初點(diǎn)擊了"好")
PHAuthorizationStatusRestricted, 因?yàn)榧议L(zhǎng)控制, 導(dǎo)致應(yīng)用無(wú)法方法相冊(cè)(跟用戶的選擇沒(méi)有關(guān)系)
*/
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusRestricted) {
// 因?yàn)榧议L(zhǎng)控制,導(dǎo)致應(yīng)用無(wú)法訪問(wèn)相冊(cè)(與用戶沒(méi)有關(guān)系)
[SVProgressHUD showErrorWithStatus:@"因?yàn)橄到y(tǒng)原因,無(wú)法訪問(wèn)系統(tǒng)相冊(cè)"];
}else if (status == PHAuthorizationStatusDenied){
// 用戶點(diǎn)擊了不允許
CLLog(@"設(shè)置-隱私-照片-百思不得姐xx_cc-允許");
}else if (status == PHAuthorizationStatusAuthorized){
// 獲得用戶授權(quán),在這里保存圖片
[self saveImage];
}else if (status == PHAuthorizationStatusNotDetermined){
// 用戶還沒(méi)有選擇進(jìn)行授權(quán)
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
// 用戶點(diǎn)擊好或者不允許 都會(huì)到這里锋叨,如果不允許則什么都不做垄分,如果好,則保存圖片
if (status == PHAuthorizationStatusAuthorized) {
// 保存圖片
[self saveImage];
}
}];
}
}
/** 保存圖片到相冊(cè) */
- (void)saveImage
{
// PHAsset : 一個(gè)資源, 比如一張圖片\一段視頻
// PHAssetCollection : 一個(gè)相簿
// PHAsset的標(biāo)識(shí), 利用這個(gè)標(biāo)識(shí)可以找到對(duì)應(yīng)的PHAsset對(duì)象(圖片對(duì)象)
__block NSString *assetLocalIdentifier = nil;
// 如果想對(duì)"相冊(cè)"進(jìn)行修改(增刪改), 那么修改代碼必須放在[PHPhotoLibrary sharedPhotoLibrary]的performChanges方法的block中
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 1.保存圖片A到"相機(jī)膠卷"中
// 創(chuàng)建圖片的請(qǐng)求
assetLocalIdentifier = [PHAssetCreationRequest creationRequestForAssetFromImage:self.imageView.image].placeholderForCreatedAsset.localIdentifier;
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// 這個(gè)方法在子線程中執(zhí)行娃磺,所以需要返回到主線程中去修改UI
if (success == NO) {
[self showError:@"保存圖片失敗!"];
return;
}
// 2.獲得相簿
PHAssetCollection *createdAssetCollection = [self createdAssetCollection];
if (createdAssetCollection == nil) {
// 這個(gè)方法在子線程中執(zhí)行锋喜,所以需要返回到主線程中去修改UI
[self showError:@"創(chuàng)建相簿失敗!"];
return;
}
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 3.添加"相機(jī)膠卷"中的圖片A到"相簿"D中
// 獲得圖片
PHAsset *asset = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetLocalIdentifier] options:nil].lastObject;
// 添加圖片到相簿中的請(qǐng)求
PHAssetCollectionChangeRequest *request = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:createdAssetCollection];
// 添加圖片到相簿
[request addAssets:@[asset]];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// 這個(gè)方法在子線程中執(zhí)行,所以需要返回到主線程中去修改UI
if (success == NO) {
[self showError:@"保存圖片失敗!"];;
} else {
[self showSuccess:@"保存圖片成功!"];;
}
}];
}];
}
/**
* 獲得相簿
* 如果已經(jīng)找到應(yīng)用對(duì)應(yīng)的相簿則直接添加到相簿豌鸡,如果沒(méi)有找到則創(chuàng)建新的相簿
*/
- (PHAssetCollection *)createdAssetCollection
{
// 相簿名字 CLAssetCollectionTitle
static NSString * CLAssetCollectionTitle = @"百思不得姐xx_cc";
// 從已存在相簿中查找這個(gè)應(yīng)用對(duì)應(yīng)的相簿
PHFetchResult<PHAssetCollection *> *assetCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (PHAssetCollection *assetCollection in assetCollections) {
if ([assetCollection.localizedTitle isEqualToString:CLAssetCollectionTitle]) {
return assetCollection;
}
}
// 沒(méi)有找到對(duì)應(yīng)的相簿, 得創(chuàng)建新的相簿
// 錯(cuò)誤信息
NSError *error = nil;
// PHAssetCollection的標(biāo)識(shí), 利用這個(gè)標(biāo)識(shí)可以找到對(duì)應(yīng)的PHAssetCollection對(duì)象(相簿對(duì)象)
__block NSString *assetCollectionLocalIdentifier = nil;
// 這個(gè)方法在主線程張中執(zhí)行嘿般,等相簿創(chuàng)建完畢之后才會(huì)返回
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
// 創(chuàng)建相簿的請(qǐng)求 CLAssetCollectionTitle 表示相簿名字
assetCollectionLocalIdentifier = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:CLAssetCollectionTitle].placeholderForCreatedAssetCollection.localIdentifier;
} error:&error];
// 如果有錯(cuò)誤信息
if (error) return nil;
// 獲得剛才創(chuàng)建的相簿
return [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[assetCollectionLocalIdentifier] options:nil].lastObject;
}
// 注意:因?yàn)樘砑訄D片到相簿,和創(chuàng)建相簿都在子線程中執(zhí)行涯冠,所以修改UI需要回到主線程炉奴,我們這里進(jìn)行了封裝。
- (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];
});
}
雖然將圖片保存到自己創(chuàng)建的項(xiàng)目相簿中比較繁瑣蛇更,但是寫一次基本上就可以循環(huán)利用了瞻赶,上面的代碼稍作修改完全可以使用到別的項(xiàng)目中。
總結(jié)
今天主要完成了cell內(nèi)部的一些細(xì)節(jié)操作派任,計(jì)算cell的高度砸逊,顯示cell內(nèi)容,查看圖片等等掌逛,同時(shí)對(duì)精華模塊進(jìn)行重構(gòu)师逸,使得精華模塊的結(jié)構(gòu)更加清晰。
看一下第五天成果
文中如果有不對(duì)的地方歡迎指出豆混。我是xx_cc篓像,一只長(zhǎng)大很久但還沒(méi)有二夠的家伙。