FlipBook: View Recording in Swift

FlipBook

一个用于录制视图的 Swift 包。可以录制视图并写入视频、gif 或 Live Photo。 还可以从图像数组创建视频、gif 和 Live Photo。

特性

要求

安装

使用 Xcode 内置的 Swift Package Manager 集成。

用法

该包的主要对象是 FlipBook 对象。 使用它可以录制视图,从图像数组创建资源,并将 Live Photo 保存到用户的照片库。 还有其他特定的编写器对象(FlipBookAssetWriterFlipBookLivePhotoWriterFlipBookGIFWriter),用于更好地控制如何生成资源。 但总的来说,FlipBook 是您用于轻松捕获视图和从图像轻松创建资源的类。

录制视图

首先创建一个 FlipBook 实例并将 assetType 设置为所需类型。 接下来,通过调用 start 来启动录制,传入您要录制的视图、一个可选的进度闭包(当资产创建进度发生时将被调用)以及一个完成闭包(完成后将返回资产)。 要停止录制,请调用 stop(),这将触发资产创建开始。 例如

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) { ... }
}

您可以查看完整的 iOS 示例macOS 示例。 在 macOS 上,请记住将 wantsLayer 设置为 true,因为 FlipBook 依赖于渲染 CALayer 以进行快照。

从图像创建资源

同样,首先创建一个 FlipBook 实例并将 assetType 设置为所需类型。 从图像创建资源时,设置 preferredFramesPerSecond 也很重要,因为它将决定资产的整体持续时间。 为了获得最佳效果,您要包含的所有图像都应具有相同的大小。 最后,调用 makeAsset,传入您要包含的图像、一个进度闭包和一个完成闭包。 例如

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
            }
        }
    }
}

高级用法

FlipBook 适用于大多数视图动画和交互,但是许多 CoreAnimation 动画和效果将无法使用上述简单的启动和停止方法。 但是,有一个可选的 animationComposition 闭包,类型为 ((CALayer) -> Void)?,它允许您使用 AVVideoCompositionCoreAnimationToolCALayer 动画和效果与 FlipBook 视频合成。 例如

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()
        }
    }
}

使用上面的代码生成 gif,您应该得到类似的东西

Animated gif of gradient layer composition

其中卡片视图是由 FlipBook 录制的背景视图,渐变描边是覆盖在录制之上的图层。 请记住,AVVideoCompositionCoreAnimationTool 的原点位于左下角,而不是像 UIKit 那样位于左上角。

何时使用

FlipBook 是捕获视图动画和交互或从松散的图像集合组成视频、gif 或 Live Photo 的好方法。 它非常适合只针对屏幕或窗口的一部分。 不仅可以创建视频,还可以创建动画 gif 和 Live Photo。

但是,对于录制长时间的用户会话或在性能达到极限时,它可能不是最佳选择。 对于这些情况,ReplayKit 可能是一个更好的解决方案。 此外,如果系统音频很重要,FlipBook 当前不捕获任何音频,而 ReplayKit 会捕获音频。

注意保护用户的敏感信息和数据非常重要; 不要录制可能包含用户不想录制的信息的屏幕。

已知问题

生成的资源示例

您可以在这里找到生成的资源库。 您也可以阅读更多关于 FlipBook 的动机。

联系方式

Brad Gayman

@bgayman

致谢

灵感来自

许可证

FlipBook 在 MIT 许可下发布。

版权所有 (c) 2020 Brad Gayman

特此授予任何人免费获得本软件及相关文档文件(“软件”)副本的许可,以无限制地处理本软件,包括但不限于使用、复制、修改、合并的权利、发布、分发、再许可和/或销售本软件的副本,并允许向被提供本软件的人员这样做,但须符合以下条件

以上版权声明和本许可声明应包含在本软件的所有副本或重要部分中。

本软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于适销性、适用于特定用途和非侵权性的保证。 在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权行为或其他方面,由本软件或本软件的使用或其他交易引起、产生或与之相关的任何索赔、损害或其他责任。