iGetIt iGetIt - 2 months ago 24
Swift Question

How to start ReplayKit screen recording in SpriteKit SKScene class

I have implemented

ReplayKit
in my
SpriteKit
game, but since everything is done within the
GameViewController
the record button appears too early. Please see my
GameViewController
class below:

class GameViewController: UIViewController, RPPreviewViewControllerDelegate {

var videoRecButton: UIButton!
var videoRecImage: UIImage!

override func viewDidLoad() {
super.viewDidLoad()

let skView = self.view as? SKView

if skView?.scene == nil {
skView?.showsFPS = true
skView?.showsNodeCount = true
skView?.showsPhysics = true
skView?.ignoresSiblingOrder = false

//starting the game with the Poster Scene
let posterScene = PosterScene(size: skView!.bounds.size)
posterScene.scaleMode = .aspectFill
skView?.presentScene(posterScene)
}

videoRecButton = UIButton(type: .custom)
videoRecImage = UIImage(named:"videoRecButton.png")

videoRecButton.frame = CGRect(x:0, y: 0, width: (videoRecImage?.size.width)!, height: (videoRecImage?.size.height)!)
videoRecButton.setImage(videoRecImage, for: .normal)
videoRecButton.addTarget(self, action:#selector(self.videoRecButtonClicked), for: .touchUpInside)
self.view.addSubview(videoRecButton)
}

func videoRecButtonClicked() {
print("Button Clicked")
startRecording()
}

func startRecording() {
let recorder = RPScreenRecorder.shared()

recorder.startRecording{ [unowned self] (error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
} else {

self.videoRecButton.addTarget(self, action:#selector(self.stopRecording), for: .touchUpInside)
}
}
}

func stopRecording() {
let recorder = RPScreenRecorder.shared()

recorder.stopRecording { [unowned self] (preview, error) in
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(self.startRecording))

if let unwrappedPreview = preview {
unwrappedPreview.previewControllerDelegate = self
self.present(unwrappedPreview, animated: true)
}
}
}

func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismiss(animated: true)
}

override var shouldAutorotate: Bool {
return true
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}

override var prefersStatusBarHidden: Bool {
return true
}
}



  1. How can I call the
    startRecording
    and
    stopRecording
    functions from a class that inherits from
    SKScene
    like
    GameScene
    class?

  2. How can enable, disable and hide the
    videoRecButton
    button from the
    GameScene
    class?



UPDATE

Based on an answer from crashoverride777 a placed the following code in my
SKScene
class but the screen records for just a few seconds before the navigation controller with a preview of the recorded video appears. The recorded video is just a black screen and the cancel and save buttons are unresponsive.

func startRecording() {
let recorder = RPScreenRecorder.shared()

if #available(iOS 10.0, *) {
recorder.startRecording{ [unowned self] (error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
} else {

self.stopRecording()

}
}
} else {
// Fallback on earlier versions
}
}

func stopRecording() {
let recorder = RPScreenRecorder.shared()

recorder.stopRecording { [unowned self] (preview, error) in
self.view?.window?.rootViewController?.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(self.startRecording))

if let unwrappedPreview = preview {
unwrappedPreview.previewControllerDelegate = self
self.view?.window?.rootViewController?.present(unwrappedPreview, animated: true)
}
}

}

func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
view?.window?.rootViewController?.dismiss(animated: true)
}


I created a record button:

let videoRecButtonSprite = SKSpriteNode(imageNamed: "videoButton")
videoRecButtonSprite.position = CGPoint(x: self.frame.width/15, y: self.frame.height - self.frame.height/12)
self.addChild(videoRecButtonSprite)

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

for touch: AnyObject in touches {
let location = touch.location(in: self)
if videoRecButtonSprite.contains(location){
startRecording()
}
}
}

Answer

You should not create your button in the GameViewController, create it directly in your SKScene. Its not good practice to use the GameViewController for UI in SpriteKit games.

There is plenty tutorials around on how to create buttons in SpriteKit.

In regards to ReplayKit, you can use it directly in the SKScene you want, just take the code you have already and move it to the relevant Scene.

To present the preview view controller in a SKScene you can say this

view?.window?.rootViewController?.present(unwrappedPreview, animated: true)

I also noticed you are presenting the View controller after you stop recording. You sure you want to do this? Usually you have a separate button in your game over menu where you can watch the recording.

Here is the general code. I also reccomend you check out apples Sample game DemoBots.

I personally use a Singleton class to manager recording, this way its easier to manager calling all the methods incase you need it for different scenes. To start of the class crate a new swift file and add this code.

 class ScreenRecoder: NSObject {

      /// Shared instance
      static let shared = ScreenRecorder()

      /// Preview controller
      var previewController: RPPreviewViewController?

      /// Private singleton init
      private override init() { }
 }

Than to start recording add this method to the ScreenRecorder class.

func start() {
    let sharedRecorder = RPScreenRecorder.shared()

    // Do nothing if screen recording is not available
    guard sharedRecorder.isAvailable else { return }

    // Stop previous recording if necessary
    if sharedRecorder.isRecording {
        stopScreenRecording()
    }

    print("Starting screen recording")

    // Register as the recorder's delegate to handle errors.
    sharedRecorder.delegate = self

    // Start recording
    if #available(iOS 10.0, *) {
        #if os(iOS)
            sharedRecorder.isMicrophoneEnabled = true
            //sharedRecorder.isCameraEnabled = true // fixme
        #endif

        sharedRecorder.startRecording { [unowned self] error in
            if let error = error as? NSError, error.code != RPRecordingErrorCode.userDeclined.rawValue {
                print(error.localizedDescription)
                // Show alert
                return
            }
        }
    } else {
        // Fallback on earlier versions
        sharedRecorder.startRecording(withMicrophoneEnabled: true) { error in
            if let error = error as? NSError, error.code != RPRecordingErrorCode.userDeclined.rawValue {
                print(error.localizedDescription)
                // Show alert
                return
            }
        }
    }
}

To stop recording call this. You notice I am not actually showing the preview yet, I am simply storing it for later use. This is the usual way you do it.

 func stop() {
    let sharedRecorder = RPScreenRecorder.shared()

    // Do nothing if screen recording is not available
    guard sharedRecorder.isAvailable else { return }

    // Stop recording
    sharedRecorder.stopRecording { [unowned self] (previewViewController, error) in
        if let error = error {
            // If an error has occurred, display an alert to the user.
            print(error.localizedDescription)
            // Show alert
            return
        }

        print("Stop screen recording")

        if let previewViewController = previewViewController {
            // Set delegate to handle view controller dismissal.
            previewViewController.previewControllerDelegate = self

            /*
             Keep a reference to the `previewViewController` to
             present when the user presses on preview button.
             */
            self.previewViewController = previewViewController
        }
    }
}

Than create 2 extensions in the ScreenRecorder class conforming to the ReplayKit delegates.

The Preview Controller Delegate

 /// RPPreviewViewControllerDelegate
 extension ScreenRecorder: RPPreviewViewControllerDelegate {

     /// Preview controller did finish
    func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
          previewController.dismiss(animated: true, completion: nil)
    }
}

and the recording delegate

extension ScreenRecorder: RPScreenRecorderDelegate {

      /// Screen recoder did stop with error
      func screenRecorder(_ screenRecorder: RPScreenRecorder, didStopRecordingWithError error: Error, previewViewController: RPPreviewViewController?) {

         // Display the error the user to alert them that the recording failed.
        let error = error as NSError
        if error.code != RPRecordingErrorCode.userDeclined.rawValue {
            print(message: error.localizedDescription)
            // show alert
        }

         // Hold onto a reference of the `previewViewController` if not nil.
         if let previewViewController = previewViewController {
             self.previewViewController = previewViewController     
         }
      }

      /// Screen recoder did change availability
      func screenRecorderDidChangeAvailability(_ screenRecorder: RPScreenRecorder) {
           // e.g update your button UI etc
           // you can use something like delegation to pass something to your SKScenes
     }
}

And finally create a method to present the preview. Preferably you call this via a button in your game over menu.

func showPreview() {
    guard let previewViewController = previewViewController else { return }

    print("Showing screen recording preview")

    // `RPPreviewViewController` only supports full screen modal presentation.
    previewViewController.modalPresentationStyle = .fullScreen

    let rootViewController = UIApplication.shared.keyWindow?.rootViewController
    rootViewController?.present(previewViewController, animated: true, completion:nil)
}

Now you can call the methods anywhere you like in your project

ScreenRecorder.shared.start()
ScreenRecorder.shared.stop()
ScreenRecorder.shared.showPreview() // call stop before calling this

This code is pretty much straight out of DemoBots.

I think the nicest way to handle screen recording is by creating an auto-recording button in your main menu. Use UserDefaults to save the on/off state of it. If its turned on you call startRecording when your gameplay begins, and call stop recording when its game over. Than you show a preview button in your game over menu to watch the recording if the user wants to.

Hope this helps