I'm creating UIImages
from video using generateCGImagesAsynchronouslyForTimes
:
fileprivate func createImageFramesFromVideo(completion: @escaping ([UIImage]) -> Void) {
guard let url = self.videoUrl else {
return
}
let asset = AVAsset(url: url)
let imageGenerator = AVAssetImageGenerator(asset: asset)
let videoDuration = asset.duration
//getting image snapshots more or less in half the video
let time1 = CMTime(value: videoDuration.value/2 - videoDuration.value/10, timescale: videoDuration.timescale)
let time2 = CMTime(value: videoDuration.value/2 - videoDuration.value/11, timescale: videoDuration.timescale)
let time3 = CMTime(value: videoDuration.value/2 - videoDuration.value/12, timescale: videoDuration.timescale)
let time4 = CMTime(value: videoDuration.value/2, timescale: videoDuration.timescale)
let time5 = CMTime(value: videoDuration.value/2 + videoDuration.value/12, timescale: videoDuration.timescale)
let time6 = CMTime(value: videoDuration.value/2 + videoDuration.value/11, timescale: videoDuration.timescale)
let time7 = CMTime(value: videoDuration.value/2 + videoDuration.value/10, timescale: videoDuration.timescale)
var imageArray : [UIImage] = []
imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time1), NSValue(time: time2), NSValue(time: time3), NSValue(time: time4), NSValue(time: time5), NSValue(time: time6), NSValue(time: time7)]) { (time, image, time1, result, err) in
if let err = err {
print("there's an error in retrieving the images", err)
}
let theImage = UIImage(cgImage: image!)
imageArray.append(theImage)
if(result == .succeeded){
completion(imageArray)
}
}
}
Since the completion handler of the function is called each time a new image is created, completion()
is called multiple times.
I want to achieve that completion()
is called only after all the images (in this case 7) are created.
How can I do it?
You need to use a
DispatchGroup
to wait for several async functions to complete. You need to make sure your calls toDispatchGroup.enter()
andleave()
are balanced (called the same number of times), since when you usedispatchGroup.notify(queue:)
, the closure will only be executed onceleave()
has been called for eachenter()
.You need to call
enter
for each element of yourtimes
array (which will contain theCMTime
variablestime1
totime7
, then in the completion block ofgenerateCGImagesAsynchronously(forTimes:
, you callleave()
. This ensures that thenotify(queue:)
will only execute its closure oncegenerateCGImagesAsynchronously
called its own completion for all images.