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