I am designing an iOS application which displays a lot of images and videos to the user in a UICollectionView
. I handle videos by generating a thumbnail of the video and displaying that, the video is only loaded when the user clicks on the thumbnail. Now I store the images and videos as base64
encoded blobs
in my remote mysqli database. My question concerns the most efficient way of loading these images and videos back into the application. Currently, I use the following functions to generate thumbnails of the images as well as the video snapshots :
func RBSquareImageTo(image: UIImage, size: CGSize) -> UIImage? {
return RBResizeImage(RBSquareImage(image), targetSize: size)
}
func RBSquareImage(image: UIImage) -> UIImage? {
let originalWidth = image.size.width
let originalHeight = image.size.height
var edge: CGFloat
if originalWidth > originalHeight {
edge = originalHeight
} else {
edge = originalWidth
}
let posX = (originalWidth - edge) / 2.0
let posY = (originalHeight - edge) / 2.0
let cropSquare = CGRectMake(posX, posY, edge, edge)
let imageRef = CGImageCreateWithImageInRect(image.CGImage, cropSquare);
return UIImage(CGImage: imageRef!, scale: UIScreen.mainScreen().scale, orientation: image.imageOrientation)
}
func RBResizeImage(image: UIImage?, targetSize: CGSize) -> UIImage? {
if let image = image {
let size = image.size
let widthRatio = targetSize.width / image.size.width
let heightRatio = targetSize.height / image.size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSizeMake(size.width * heightRatio, size.height * heightRatio)
} else {
newSize = CGSizeMake(size.width * widthRatio, size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRectMake(0, 0, newSize.width, newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.drawInRect(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
} else {
return nil
}
}
func videoSnapshot(vidURL: NSURL) -> UIImage? {
let asset = AVURLAsset(URL: vidURL)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
let timestamp = CMTime(seconds: 1, preferredTimescale: 60)
do {
let imageRef = try generator.copyCGImageAtTime(timestamp, actualTime: nil)
return UIImage(CGImage: imageRef)
}
catch let error as NSError
{
print("Image generation failed with error \(error)")
return nil
}
}
I then use the following functions to compress the content as well as the thumbnails and send them to my database in a POST request :
func compressVideo(inputURL: NSURL, outputURL: NSURL, handler:(session: AVAssetExportSession)-> Void)
{
let urlAsset = AVURLAsset(URL: inputURL, options: nil)
let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality)
exportSession!.outputURL = outputURL
exportSession!.outputFileType = AVFileTypeQuickTimeMovie
exportSession!.shouldOptimizeForNetworkUse = true
exportSession!.exportAsynchronouslyWithCompletionHandler { () -> Void in
handler(session: exportSession!)
}
}
func compressImage(image:UIImage) -> NSData {
// Reducing file size to a 10th
var actualHeight : CGFloat = image.size.height
var actualWidth : CGFloat = image.size.width
let maxHeight : CGFloat = 1136.0
let maxWidth : CGFloat = 640.0
var imgRatio : CGFloat = actualWidth/actualHeight
let maxRatio : CGFloat = maxWidth/maxHeight
var compressionQuality : CGFloat = 0.5
if (actualHeight > maxHeight || actualWidth > maxWidth){
if(imgRatio < maxRatio){
//adjust width according to maxHeight
imgRatio = maxHeight / actualHeight;
actualWidth = imgRatio * actualWidth;
actualHeight = maxHeight;
}
else if(imgRatio > maxRatio){
//adjust height according to maxWidth
imgRatio = maxWidth / actualWidth;
actualHeight = imgRatio * actualHeight;
actualWidth = maxWidth;
}
else{
actualHeight = maxHeight;
actualWidth = maxWidth;
compressionQuality = 1;
}
}
let rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
UIGraphicsBeginImageContext(rect.size);
image.drawInRect(rect)
let img = UIGraphicsGetImageFromCurrentImageContext();
let imageData = UIImageJPEGRepresentation(img, compressionQuality);
UIGraphicsEndImageContext();
return imageData!;
}
This results in the images and videos being between 500KB - 1.5MB and the thumbnails about 4-6 KB. I have tested this only with an ipad2, hence I suspect these sizes will increase significantly if a newer iPhone is used for taking the photos and videos. What I am currently doing is sending one request to my server, which returns the thumbnails of all the images and videos in the database, however the thumbnails are generally of low quality. I am wondering if there is a more efficient way of loading the full images and videos in this particular situation. I was thinking of doing something like sending a request which loads about 10 images only (the ones visible on screen) and when the user scrolls down, another request is sent for the next 10 images, the problem with this is, the cells will be blank until the request is complete if the user scrolls down fast enough. Is there any libraries you would recommend for this or maybe another approach which you would say is more efficient? Should I be using base64 encoding at all in this case?