往期文章
寻找IOS相册中相似图片
NSNotification与类对象,实例对象
CocoaPods私有源搭建
雷达扩散效果搜索设备
实现一个简单沙盒文件浏览器(一)
在上一节中我们实现了简单沙盒文件浏览器的导入数据功能,文件类型判断功能。在今天这一节中,我们主要实现沙盒文件浏览器文件的展示。
目录数据列表获取
获取目录下文件夹和文件的路径
在这里我们沙盒浏览器展示的根目录是Documents,所以我们首先需要获取Document文件夹下,所有的文件以及文件夹
- (nullable NSArray<NSString *> *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error
使用NSFileManager提供的方法,我们可以成功获取的该目录下所有文件以及文件夹的名称。
注意 该方法对当前目录进行浅搜索,所谓浅搜索也就是不会遍历任何符号链接(软链接)。不会搜索该目录下任何子目录的内容,也不会返回当前目录(.) 以及当前目录的父目录(..)。但是该方法会返回当前目录中的隐藏文件以及隐藏目录比如(.Trash目录)
返回的形式是一个文件名数组
@[@"Folder1",@"Folder2",@"p1.jpg".@"m1.mov"]
获取文件名和文件夹的元数据(metadata)
为了获取文件的metadata,同样我们需要使用NSFileManager的提供的方法
- (nullable NSDictionary<NSFileAttributeKey, id> *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error
这里需要传入的path是文件夹和文件的绝对路径,而上一步我们获取的path是相对于父目录(Documents)的相对路径,所以我们需要将父目录(Documents)的path后面拼接上我们的文件名。从而得到绝对路径。
返回的元数据如下
{
NSFileCreationDate = "2021-09-12 00:37:31 +0000";
NSFileExtensionHidden = 0;
NSFileGroupOwnerAccountID = 501;
NSFileGroupOwnerAccountName = mobile;
NSFileModificationDate = "2021-09-12 00:37:31 +0000";
NSFileOwnerAccountID = 501;
NSFileOwnerAccountName = mobile;
NSFilePosixPermissions = 420;
NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication;
NSFileReferenceCount = 1;
NSFileSize = 2070195;
NSFileSystemFileNumber = 2911611;
NSFileSystemNumber = 16777222;
NSFileType = NSFileTypeRegular;
}
文件缩略图获取
经过上面的步骤之后,我们已经成功拿到的需要展示在沙盒文件浏览器中的数据。并且在上一节中我们知道了可以通过UTType来区分不同类型的文件,像文档,目录这样的文件,我们可以使用固定的图片来展示其icon,但是像图片和视频之类的文件就需要我们手动去生成缩略图。
图片获取缩略图
首先如果我们不使用缩略图的话,直接读取沙盒文件中的原图。会造成两个问题
第一如果沙盒里面图片过多,就会读取大量的图片数据UIImage到内存中,从而造成内存暴涨。
第二,每次都读取原图的会使用大量的IO影响我们程序的性能
所以我们需要去对沙盒里面的图片进行缩略图生成,这里我们使用ImageIO给我们提供的缩略图生成方法
+ (UIImage *)getThumbnailImageWithPath:(NSString *)path
{
CGImageSourceRef imageSource;
imageSource = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL);
if (imageSource == nil) {
return nil;
}
// 图片宽高
int imageSize = 100*2;
// 缩略图尺寸
CFNumberRef thumbSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);
CFTypeRef imageValues[3];
CFStringRef imageKeys[3];
imageKeys[0] = kCGImageSourceCreateThumbnailWithTransform;
imageValues[0] = (CFTypeRef)kCFBooleanTrue;
imageKeys[1] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
imageValues[1] = (CFTypeRef)kCFBooleanTrue;
//缩放键值对
imageKeys[2] = kCGImageSourceThumbnailMaxPixelSize;
imageValues[2] = (CFTypeRef)thumbSize;
CFDictionaryRef imageOption = CFDictionaryCreate(NULL, (const void **) imageKeys,
(const void **) imageValues, 3,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
//获取缩略图
CGImageRef thumbImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, imageOption);
CFRelease(imageOption);
CFRelease(imageSource);
CFRelease(thumbSize);
UIImage* thumbnailImage = [UIImage imageWithCGImage:thumbImage];
//显示缩略图
return thumbnailImage;
}
注意这里使用的是CoreFoundation,不支持ARC,所以记得使用CFRelease释放申请的对象,不然会造成内存泄漏
视频缩略图获取
使用AVAssetImageGenerator来获取视频的某一帧作为图片。
+ (UIImage *)getImageForVideoWithURLStr:(NSString *)urlStr
{
if (!urlStr.length) return nil;
UIImage *shotImage;
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:urlStr] options:nil];
AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
gen.appliesPreferredTrackTransform = YES;
CMTime time = CMTimeMakeWithSeconds(0.0, 600);
NSError *error = nil;
CMTime actualTime;
CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
shotImage = [[UIImage alloc] initWithCGImage:image];
CGImageRelease(image);
return shotImage;
}
我们根据沙盒中视频的路径,生成了一个AVURLAsset对象,然后通过这个AVURLAsset对象构造出一个AVAssetImageGenerator。
初始化AVAssetImageGenerator的参数 并设置CMTime time = CMTimeMakeWithSeconds(0.0, 600);
CMTime CMTimeMakeWithSeconds(
Float64 seconds, //第几秒的截图,是当前视频播放到的帧数的具体时间
int32_t preferredTimeScale //首选的时间尺度 "每秒的帧数"
);
第一个参数seconds代表截取视频的第几秒作为截图
第二个参数代表每秒的帧数
这里我们希望获取第一帧图片,所以第一个参数为0代表第0秒,第二个参数尽可能大
接下来我们在传入一个actualTime,用来表示AVAssetImageGenerator生成视频截图的确切时间。
将两个CMTime传入生成截图
[gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
缩略图的缓存
参考SDWebImage每次下载完图片之后,都会进行缓存从而避免多次下载相同的资源。所里这里我们也参看SDWebImage的原理,每次生成沙盒视频和图片的缩略图之后,将生成的好的图片缓存在沙盒的一个目录中
如下图所示,我在tmp文件夹下创建了一个ImageCache的目录用来保存生成好的缩略图
然后将生成好的缩略图按照原图的绝对路径的hash值,作为缩略图的文件名存入我们生成好的缓存目录中。
在沙盒文件浏览器展示沙盒文件的读取图片缩略图的时候,先去缓存文件夹中查找,如果有就立刻返回对于的缩略图,如果没有在进行缩略图获取。在成功获取到缩略图之后。立刻缓存获取的缩略图都缓存文件夹中。
实现代码如下:
- (UIImage*) getSanboxThumbnailAtPath:(AMSanboxFileModel*) model {
//尝试去缓存路面取image
UIImage* image = [self getCacheSanboxImage:model.fileName];
if (image) {
//如果获取到缓存文件则立刻返回缓存文件
return image;
}
else {
//如果没有获取到缓存文件,则调用各自缩略图请求器进行请求缩略图
if (model.type == AMSanboxImage) {
image = [AMImageManager getThumbnailImageWithPath:model.path];
}
else if (model.type == AMSanboxViedo){
image = [AMAVAssetManager getImageForVideoWithURLStr:model.path];
}
//写入缓存
[self cacheSanboxImage:image forKey:model.fileName];
return image;
}
return nil;
}
/// 根据key获取缓存图片
/// **@param** key 图片名称
- (UIImage*) getCacheSanboxImage:(NSString*) key {
NSString* cachePath = [self createFolderWithPath:[self defaultDiskCacheDirectory]];
NSString* fileName = [cachePath stringByAppendingPathComponent:key];
NSData* imageData = [NSData dataWithContentsOfFile:fileName];
return imageData ? [UIImage imageWithData:imageData] : nil;
}
///缓存UIImage
- (void) cacheSanboxImage:(UIImage*) image forKey:(NSString*) key{
//创建保存目录,如果存在就直接返回目录,如果不存在就创建目录
NSString* cachePath = [self createFolderWithPath:[self defaultDiskCacheDirectory]];
NSData* imageData = UIImagePNGRepresentation(image);
NSString* fileName = [cachePath stringByAppendingPathComponent:key];
bool writeError = NO;
writeError = [imageData writeToFile:fileName atomically:NO];
if (!writeError) {
NSLog(@"写入缓存成功");
}
else {
NSLog(@"写入缓存失败");
}
}
这样一来我们的沙盒浏览器就能成功的显示沙盒文件中的缩略图了