The articles

CocoaPods private source build radar diffusion effect search device implement a simple sandbox file browser

In the last section, we realized the function of simple sandbox file browser to import data, file type judgment function. In today’s section, we will focus on the sandbox file browser file presentation.

Obtain directory data list

Gets the paths of folders and files under the directory

Here our sandbox browser shows the root directory as Documents, so first we need to get all the files and folders in the Document folder

- (nullable NSArray<NSString *> *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error
Copy the code

Using methods provided by NSFileManager, we can successfully obtain the names of all files and folders in the directory.

Note that this method does a shallow search of the current directory, meaning that it does not traverse any symbolic links (soft links). The contents of any subdirectories under the directory are not searched and the current directory is not returned (.) And the parent directory of the current directory (..) . But this method returns hidden files in the current directory and hidden directories such as (.trash)

The return form is an array of file names

@ [@"Folder1".@"Folder2".@"p1.jpg".@"m1.mov"]
Copy the code

Get metadata for file names and folders

To retrieve file metadata, we also need to use the methods provided by NSFileManager

- (nullable NSDictionary<NSFileAttributeKey.id> *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error
Copy the code

The path we need to pass in here is the absolute path of the folder and file, while the path we obtained in the previous step is the relative path to the parent directory (Documents), so we need to concatenate our file name after the path of the parent directory (Documents). So you get an absolute path.

The metadata returned is as follows

{

NSFileCreationDate = "The 2021-09-12 00:37:31 + 0000";

NSFileExtensionHidden = 0;

NSFileGroupOwnerAccountID = 501;

NSFileGroupOwnerAccountName = mobile;

NSFileModificationDate = "The 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;

}
Copy the code

Obtain file thumbnail

After the above steps, we have successfully retrieved the data we need to display in the sandbox file browser. In the last section, we learned that UTType can be used to distinguish different types of files, such as documents and directories. We can use fixed pictures to display their ICONS, but for files like pictures and videos, we need to manually generate thumbnails.

Image get thumbnail

First, if we don’t use thumbnails, we just read the original image in the sandbox file. This creates two problems

First, if there are too many images in the sandbox, it will read a lot of image data UIImage into memory, causing memory inflation.

Second, reading the original image every time will use a lot of IO and affect the performance of our program

So we need to go to the sandbox image thumbnail generation, here we use the thumbnail generation method provided by ImageIO

+ (UIImage *)getThumbnailImageWithPath:(NSString *)path

{
    CGImageSourceRef imageSource;
    imageSource = CGImageSourceCreateWithURL((CFURLRef) [NSURL fileURLWithPath:path], NULL);
    if (imageSource == nil) {
        return nil;
    }

    // Image width and height
    int imageSize = 100*2;
    // Thumbnail size
    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;

    // Scale key-value pairs

    imageKeys[2] = kCGImageSourceThumbnailMaxPixelSize;
    imageValues[2] = (CFTypeRef)thumbSize;

    CFDictionaryRef imageOption = CFDictionaryCreate(NULL, (const void **) imageKeys,

                                      (const void **) imageValues, 3,

                                      &kCFTypeDictionaryKeyCallBacks,

                                      &kCFTypeDictionaryValueCallBacks);
    // Get the thumbnail

    CGImageRef thumbImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, imageOption);

    CFRelease(imageOption);
    CFRelease(imageSource);
    CFRelease(thumbSize);

    UIImage* thumbnailImage = [UIImage imageWithCGImage:thumbImage];
    // Display thumbnails
    return thumbnailImage;

}
Copy the code

Note that this is CoreFoundation and does not support ARC, so remember to use CFRelease to release the requested object, otherwise you will cause a memory leak

Video thumbnail capture

Use the AVAssetImageGenerator to get a frame of the video as a picture.

+ (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;
}
Copy the code

We generate an AVURLAsset object based on the path of the video in the sandbox, and then construct an AVAssetImageGenerator from this AVURLAsset object.

Initialize AVAssetImageGenerator and set CMTime time = CMTimeMakeWithSeconds(0.0, 600);

CMTime CMTimeMakeWithSeconds(
     Float64 seconds,   // The screenshots of seconds are the specific time of the current video playing frame
     int32_t preferredTimeScale // Preferred time scale "frames per second"
 );
Copy the code

The first parameter, seconds, represents the number of seconds in which the video was captured as a screenshot

The second parameter represents the number of frames per second

Here we want to get the first frame, so the first parameter is 0 for the 0th second, and the second parameter is as large as possible

Next we pass in an actualTime to indicate the exact time when AVAssetImageGenerator generates the video screenshot.

Pass in two cmtimes to generate a screenshot

[gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
Copy the code

Caching of thumbnails

See SDWebImage. After each image is downloaded, it is cached to avoid downloading the same resource multiple times. So here we also look at the principle of SDWebImage. After generating the thumbnails of the sandbox video and image, the generated good image is cached in a directory in the sandbox as shown below. I created an ImageCache directory under the TMP folder to store the generated thumbnails

Then the generated thumbnail is stored in the generated cache directory according to the hash value of the absolute path of the original thumbnail as the file name of the thumbnail.

When the sandbox browser displays the thumbnail of the read image of the sandbox file, go to the cache folder first and return the thumbnail of the read image immediately if there is one, if there is no thumbnail retrieval. After successfully obtaining the thumbnail image. Cached thumbnails are cached immediately in the cache folder.

The implementation code is as follows:

- (UIImage*) getSanboxThumbnailAtPath:(AMSanboxFileModel*) model {

    // Try to cache the road image
    UIImage* image = [self getCacheSanboxImage:model.fileName];

    if (image) {
        // If the cache file is retrieved, the cache file is returned immediately
        return image;
    }

    else {
        // If no cache file is retrieved, the respective thumbnail requestor is called to request the thumbnail
        if (model.type == AMSanboxImage) {
            image = [AMImageManager getThumbnailImageWithPath:model.path];
        }

        else if (model.type == AMSanboxViedo){
            image = [AMAVAssetManager getImageForVideoWithURLStr:model.path];
        }
        // Write cache
        [self cacheSanboxImage:image  forKey:model.fileName];
        return image;
    }
    return nil;
}

// get the cache image by key
// **@param** key image name
- (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;

}

/ / / the cache UIImage
- (void) cacheSanboxImage:(UIImage*) image forKey:(NSString*) key{

    // Create a save directory and return it if it exists, or create it if it doesn't exist

    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(Write cache successful);
    }

    else {
        NSLog(@" Write cache failed"); }}Copy the code

This way our sandbox browser can successfully display the thumbnail image in the sandbox file