六天完成一個(gè)簡(jiǎn)單iOS App - 第五天

第五天任務(wù)

今天主要完成精華頁(yè)面中cell內(nèi)內(nèi)容的處理漆枚。

  1. cell高度的計(jì)算
  2. cell中間內(nèi)容的顯示
  3. 精華模塊的重構(gòu)
  4. 查看圖片
  5. 保存圖片到相冊(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高度計(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)分別描述視頻,音頻两嘴,和圖片的顯示丛楚。如圖

視頻xib
音頻xib
圖片xib

這里圖片里面又分為普通圖片、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é)處理距糖。

  1. 長(zhǎng)圖顯示的處理玄窝,此時(shí)我們看到的長(zhǎng)圖的顯示是這樣的


    未處理長(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;
  1. 圖片顯示進(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;
}];
  1. 視頻和音頻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];
}
  1. 監(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ò)比較選擇最好的一種蟀悦。

  1. 通過(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)容由其自己管理的代碼原則浙炼。

  1. 給基類添加一個(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)行判斷,延展性非常不好诀豁。

  1. 通過(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


CLSeeBigViewController的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)相簿

如果僅僅是將圖片保存到系統(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ō)明


UIImageWriteToSavedPhotosAlbum - API

訪問(wèn)系統(tǒng)相冊(cè)需要獲得用戶授權(quán)抱虐,且只會(huì)請(qǐng)求一次,如果用戶點(diǎn)擊了不允許饥脑,則永遠(yuǎn)不允許訪問(wèn)相冊(cè)恳邀,此時(shí)需要提醒用戶去[設(shè)置]-[隱私]-[照片]中開(kāi)啟。


取得用戶授權(quán)

我們這里想要實(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)有二夠的家伙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皿伺,一起剝皮案震驚了整個(gè)濱河市员辩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸵鸥,老刑警劉巖奠滑,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異妒穴,居然都是意外死亡宋税,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門宰翅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)弃甥,“玉大人爽室,你說(shuō)我怎么就攤上這事汁讼∠ィ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵嘿架,是天一觀的道長(zhǎng)瓶珊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)耸彪,這世上最難降的妖魔是什么伞芹? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蝉娜,結(jié)果婚禮上唱较,老公的妹妹穿的比我還像新娘。我一直安慰自己召川,他們只是感情好南缓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著荧呐,像睡著了一般汉形。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倍阐,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天概疆,我揣著相機(jī)與錄音,去河邊找鬼峰搪。 笑死岔冀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的概耻。 我是一名探鬼主播楣颠,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咐蚯!你這毒婦竟也來(lái)了童漩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤春锋,失蹤者是張志新(化名)和其女友劉穎矫膨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體期奔,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侧馅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呐萌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馁痴。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肺孤,靈堂內(nèi)的尸體忽然破棺而出罗晕,到底是詐尸還是另有隱情济欢,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布小渊,位于F島的核電站法褥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏酬屉。R本人自食惡果不足惜半等,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呐萨。 院中可真熱鬧杀饵,春花似錦、人聲如沸谬擦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怯屉。三九已至蔚舀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锨络,已是汗流浹背赌躺。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羡儿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肤粱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子厨相,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容