FlipBook
A swift package for recording views
Install / Use
/learn @bgayman/FlipBookREADME
![]()
FlipBook
A swift package for recording views. Record a view and write to video, gif, or Live Photo. Also, create videos, gifs, and Live Photos from an array of images.
Features
- Record a view over time
- Write recording to video
- Write recording to .gif
- Compose recording into a Live Photo
- Create asset (video, .gif, Live Photo) from an array of images
Requirements
- iOS 10.0
- tvOS 10.0
- macOS 10.15
- Xcode 11
- Swift 5.1
Installation
Use Xcode's built in integration with Swift Package Manager.
- Open Xcode
- Click File -> Swift Packages -> Add Package Dependency
- In modal that says "Choose Package Repository" paste https://github.com/bgayman/FlipBook.git and press return
- Select version range you desire (default selection works well)
- Xcode will add the package to your project
- In any file where you want to use FlipBook add
import FlipBook
Usage
The main object of the package is the FlipBook object. With it, you can record a view, create an asset from an array of images, and save a Live Photo to the users photo library. There are other specific writer objects (FlipBookAssetWriter, FlipBookLivePhotoWriter, and FlipBookGIFWriter) for more control over how assets are generated. But, by and large, FlipBook is the class that you'll use for easy view capture and easy asset creation from images.
Recording a View
Begin by creating an instance of FlipBook and setting the assetType to desired. You'll next start the recording by calling start, passing in the view you wish to record, an optional progress closure that will be called when asset creation progress has been made, and a completion closure that will return the asset when you're done. To stop the recording, call stop() which will trigger the asset creation to begin. For example:
import UIKit
import FlipBook
class ViewController: UIViewController {
// Hold a refrence to `flipBook` otherwise it will go out of scope
let flipBook = FlipBook()
@IBOutlet weak var myAnimatingView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the assetType we want to create
flipBook.assetType = .video
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: animated)
// Start recording when we appear, here we're recording the root view of `ViewController` but could record any arbitary view
flipBook.startRecording(view) { [weak self] result in
// Switch on result
switch result {
case .success(let asset):
// Switch on the asset that's returned
switch asset {
case .video(let url):
// Do something with the video
// We expect a video so do nothing for .livePhoto and .gif
case .livePhoto, .gif:
break
}
case .failure(let error):
// Handle error in recording
print(error)
}
}
// In this example we want to record some animation, so after we start recording we kick off the animation
animateMyAnimatingView {
// The animation is done so stop recording
self.flipBook.stop()
}
}
private func animateMyAnimatingView(_ completion: () -> Void) { ... }
}
You can checkout a complete iOS example and macOS example. On macOS, remember to set wantsLayer to true as FlipBook depends on rendering CALayers for snapshotting.
Creating an Asset from Images
Similarly, begin by creating an instance of FlipBook and setting the assetType desired. When creating an asset from Images it is also important to set the preferredFramesPerSecond as this will determine the overall duration of the asset. For best results, it is also important that all of the images you wish to include are the same size. Finally, you call makeAsset passing in the images you want to include, a progress closure, and a completion closure. For example:
import UIKit
import FlipBook
class ViewController: UIViewController {
// Hold a refrence to `flipBook` otherwise it will go out of scope
let flipBook = FlipBook()
override func viewDidLoad() {
super.viewDidLoad()
// Set `assetType` to the asset type you desire
flipBook.assetType = .video
// Set `preferredFramesPerSecond` to the frame rate of the animation images
flipBook.preferredFramesPerSecond = 24
// Load the images. More realistically these would likely be images the user created or ones that were stored remotely.
let images = (1 ... 48).compactMap { UIImage(named: "animationImage\($0)") }
// Make the asset
flipBook.makeAsset(from: images) { [weak self] (result) in
switch result {
case .success(let asset):
// handle asset
case .failure(let error):
// handle error
}
}
}
}
Advanced Usage
FlipBook will work for most view animations and interactions however many CoreAnimation animations and effects will not work with the simple start and stop method described above. However, there is an optional animationComposition closure of type ((CALayer) -> Void)? that will allow you to composite CALayer animations and effects with a FlipBook video using the AVVideoCompositionCoreAnimationTool. For example:
import UIKit
import FlipBook
class ViewController: UIViewController {
// Hold a refrence to `flipBook` otherwise it will go out of scope
let flipBook = FlipBook()
@IBOutlet weak var myBackgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the assetType we want to create
flipBook.assetType = .video
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: animated)
// Get the scale of the screen that we capturing on as we'll want to apply the scale when animating for the composition
let scale = view.window?.screen.scale ?? 1.0
// Start recording when we appear, here we're recording a view that will act as the background for our layer animation
flipBook.startRecording(myBackgroundView, compositionAnimation: { layer in
// create a gradient layer
let gradientLayer = CAGradientLayer()
gradientLayer.frame = layer.bounds
gradientLayer.colors = [UIColor.systemRed.cgColor, UIColor.systemBlue.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint.zero
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
// create a shape layer
let shapeLayer = CAShapeLayer()
shapeLayer.frame = layer.bounds
// remember that layer composition is in pixels not points so scale up
shapeLayer.lineWidth = 10.0 * scale
shapeLayer.lineCap = .round
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.path = UIBezierPath(ovalIn: layer.bounds.insetBy(dx: 150 * scale, dy: 150 * scale)).cgPath
shapeLayer.strokeEnd = 0.0
gradientLayer.mask = shapeLayer
layer.addSublayer(gradientLayer)
let strokeAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeAnimation.fromValue = 0.0
strokeAnimation.toValue = 1.0
strokeAnimation.duration = 8.0
// must start the animation at `AVCoreAnimationBeginTimeAtZero`
strokeAnimation.beginTime = AVCoreAnimationBeginTimeAtZero
strokeAnimation.isRemovedOnCompletion = false
strokeAnimation.fillMode = .forwards
shapeLayer.add(strokeAnimation, forKey: "strokeAnimation")
}, completion: { [weak self] result in
// Switch on result
switch result {
case .success(let asset):
// Switch on the asset that's returned
switch asset {
case .video(let url):
// Do something with the video
// We expect a video so do nothing for .livePhoto and .gif
case .livePhoto, .gif:
break
}
case .failure(let error):
// Handle error in recording
print(error)
}
})
// After 9 seconds stop recording. We'll have 8 seconds of animation and 1 second of final state
DispatchQueue.main.asyncAfter(deadline: .now() + 9.0) {
self.flipBook.stop()
}
}
}
Generating a gif with the code above you should get something like:

Where the card view is the background view recorded by FlipBook and the gradient stroke is the layer composited on top of the recording. Remember that AVVideoCompositionCoreAnimationTool has an origin in the lower left, not top left like UIKit.
When to Use
FlipBook is a great way to capture view animations and interactions or to compose a video, gif, or Live Photo from a loose collection of images. It's great for targeting just a portion of the screen or window. And for creating not just videos, but also animated gifs and Live Photos.
However, it is likely not the bes
