第五天任务
今天主要完成精华页面中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,一只长大很久但还没有二够的家伙。