Skip to content

Instantly share code, notes, and snippets.

@mbernson
Last active January 31, 2023 08:17
Show Gist options
  • Select an option

  • Save mbernson/498c15f4381931e77fe1059931483c79 to your computer and use it in GitHub Desktop.

Select an option

Save mbernson/498c15f4381931e77fe1059931483c79 to your computer and use it in GitHub Desktop.
Button that starts/stops a screen recording and present the user with the Apple-provided preview
// How to use it:
import SwiftUI
struct ContentView: View {
var body: some View {
RecordScreenButton()
}
}
//
// RecordScreenButton.swift
//
//
// Created by Mathijs Bernson on 26/01/2023.
// Copyright © 2023 Q42. All rights reserved.
//
import SwiftUI
import ReplayKit
import os.log
struct RecordScreenButton: View {
@StateObject private var model = ScreenRecorderModel()
var body: some View {
Button {
#if targetEnvironment(simulator)
model.showError(title: "Screen recording is not supported in the simulator", underlyingError: nil)
#else
if model.isRecording {
model.stopRecording()
} else {
model.startRecording()
}
#endif
} label: {
Image(systemName: model.isRecording ? "stop.circle" : "record.circle")
.foregroundColor(model.isRecording ? .red : .primary)
.animation(.easeOut, value: model.isRecording)
}
.accessibilityLabel("Record screen")
.disabled(!model.screenRecorder.isAvailable)
.sheet(isPresented: $model.isPresentingPreview) {
ScreenRecorderPreview(previewController: model.previewController!)
.ignoresSafeArea(.container, edges: .bottom)
}
.alert(isPresented: $model.isPresentingError, error: model.error) { error in
Button("Ok") {}
} message: { error in
if let failureReason = error.failureReason {
Text(failureReason)
}
}
}
}
private struct ScreenRecorderPreview: UIViewControllerRepresentable {
let previewController: RPPreviewViewController
func makeUIViewController(context: Context) -> RPPreviewViewController {
previewController
}
func updateUIViewController(_ viewController: RPPreviewViewController, context: Context) {}
}
private struct ScreenRecorderError: LocalizedError {
let errorDescription: String?
let failureReason: String?
init(title: String, underlyingError: Error?) {
self.errorDescription = title
self.failureReason = underlyingError?.localizedDescription
}
}
private class ScreenRecorderModel: NSObject, ObservableObject {
let screenRecorder = RPScreenRecorder.shared()
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ScreenRecorder")
@Published var isRecording: Bool = false
@Published var isPresentingPreview: Bool = false
var previewController: RPPreviewViewController?
@Published var isPresentingError: Bool = false
@Published var error: ScreenRecorderError?
override init() {
isRecording = RPScreenRecorder.shared().isRecording
super.init()
screenRecorder.isMicrophoneEnabled = false
screenRecorder.isCameraEnabled = false
}
func startRecording() {
isRecording = true
screenRecorder.startRecording { error in
if let error {
self.logger.error("Unable to start screen recording: \(error.localizedDescription)")
self.isRecording = false
self.showError(title: String(localized: "Unable to start screen recording"), underlyingError: error)
} else {
self.logger.info("Screen recorder started.")
}
}
}
func stopRecording() {
screenRecorder.stopRecording { previewController, error in
self.isRecording = false
if let error {
self.logger.error("Screen recording failed: \(error.localizedDescription)")
self.showError(title: String(localized: "Screen recording failed"), underlyingError: error)
} else if let previewController {
self.logger.info("Screen recorder finished. Showing preview to user.")
previewController.previewControllerDelegate = self
self.isPresentingPreview = true
self.previewController = previewController
}
}
}
func showError(title: String, underlyingError error: Error?) {
self.isPresentingError = true
self.error = .init(title: title, underlyingError: error)
}
}
extension ScreenRecorderModel: RPPreviewViewControllerDelegate {
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismissPreview()
}
func dismissPreview() {
isPresentingPreview = false
previewController = nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment