iosswiftuiscenekitarkitreplaykit

How can I record an ARKit scene but exclude UI elements?


I'm using ARKit with Scenekit for rendering. I'd like to let users capture videos of the AR session so that they can save it to their photos or share it.

Currently I'm using ARVideoKit for this, but the performance leaves something to be desired and I've run into some difficult to workaround bugs. Other libraries I've found haven't been any better.

ReplayKit seems like ideal solution but it records my entire app, including the user interface. Is there a way to get ReplayKit to record just the AR content while excluding the user interface?


Solution

  • You can use ReplayKit for this but it isn't very well documented. The key is that you render all of your UI elements in a separate UIWindow that is overlaid on top of a primary UIWindow that contains the AR content. ReplayKit only records the primary window, so with this structure the user interface elements will not show up in the recording.

    While there may be a better way to do this, here's an example of how I setup this window structure for my SwiftUI based app. Here I use the UIWindow.level property to mark the AR content as the main window, while putting the UI into its own secondary window at a higher level:

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
        var arWindow: UIWindow?    
        var uiWindow: UIWindow?
    
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options 
            if let windowScene = scene as? UIWindowScene {
                // Create a window for the AR content.
                // This is the main window. 
                let arWindow = UIWindow(windowScene: windowScene)
                self.arWindow = arWindow
                arWindow.windowLevel = .normal
                
                // Add your AR view controller here or set the view controller lazily
                // when you actually need to show AR content
                let vc = UIViewController() 
                arWindow.rootViewController = vc
                
    
                // Now create a window for the UI
                let uiWindow = UIWindow(windowScene: windowScene)
                self.uiWindow = uiWindow
                // Setting the level makes this window's content be excluded from replaykit
                uiWindow.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue + 1)
                uiWindow.isOpaque = false
                
                // Render your SwiftUI based user interface
                let content = MyUserInterfaceView()
                    .background(Color.clear)
                
                let hosting = UIHostingController(rootView: content)
                hosting.view.backgroundColor = .clear
                hosting.view.isOpaque = false
                uiWindow.rootViewController = hosting
                
                uiWindow.makeKeyAndVisible()
            }
        }
    }
    

    My app initializes the AR content lazily, so I simply update arWindow.viewController when I need to show it.

    A few notes: